'OHRRPGCE GAME - Saving and loading games '(C) Copyright 1997-2005 James Paige and Hamster Republic Productions 'Please read LICENSE.txt for GPL License details and disclaimer of liability 'See README.txt for code docs and apologies for crappyness of this code ;) ' #ifdef TRY_LANG_FB #define __langtok #lang __langtok "fb" #else OPTION STATIC OPTION EXPLICIT #endif #include "config.bi" #include "ver.txt" #include "udts.bi" #include "allmodex.bi" #include "common.bi" #include "gglobals.bi" #include "const.bi" #include "loading.bi" #include "reload.bi" #include "reloadext.bi" #include "savegame.bi" #include "moresubs.bi" #include "menustuf.bi" #include "yetmore.bi" USING Reload USING Reload.Ext '--Local subs and functions DECLARE SUB gamestate_to_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_state_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_script_to_reload(byval parent as Reload.NodePtr) DECLARE FUNCTION script_trigger_from_reload(byval node as Reload.NodePtr) as integer DECLARE SUB gamestate_globals_to_reload(byval parent as Reload.NodePtr, byval first as integer=0, byval last as integer=maxScriptGlobals) DECLARE SUB gamestate_maps_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_npcs_to_reload(byval parent as Reload.NodePtr, byval map as integer) DECLARE SUB gamestate_tags_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_onetime_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_party_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_spelllist_to_reload(byval hero_slot as integer, byval spell_list as integer, byval parent as Reload.NodePtr) DECLARE SUB gamestate_inventory_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_shops_to_reload(byval parent as Reload.NodePtr) DECLARE SUB gamestate_vehicle_to_reload(byval parent as Reload.NodePtr) DECLARE SUB new_loadgame(byval slot as integer) DECLARE SUB gamestate_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_state_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_script_from_reload(byval node as Reload.NodePtr) DECLARE SUB script_trigger_to_reload(byval parent as Reload.NodePtr, node_name as STRING, byval script_id as integer) DECLARE SUB gamestate_globals_from_reload(byval parent as Reload.NodePtr, byval first as integer, byval last as integer) DECLARE SUB gamestate_maps_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_npcs_from_reload(byval node as Reload.NodePtr, byval map as integer) DECLARE SUB gamestate_tags_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_onetime_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_party_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_spelllist_from_reload(byval hero_slot as integer, byval spell_list as integer, byval parent as Reload.NodePtr) DECLARE SUB gamestate_inventory_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_shops_from_reload(byval node as Reload.NodePtr) DECLARE SUB gamestate_vehicle_from_reload(byval node as Reload.NodePtr) DECLARE SUB rsav_warn (s as STRING) DECLARE SUB new_loadglobalvars (byval slot as integer, byval first as integer, byval last as integer) DECLARE FUNCTION new_save_slot_used (byval slot as integer) as integer DECLARE FUNCTION new_count_used_save_slots() as integer DECLARE SUB new_get_save_slot_preview(byval slot as integer, pv as SaveSlotPreview) '--old save/load support DECLARE SUB old_loadgame (byval slot as integer) DECLARE SUB old_loadglobalvars (byval slot as integer, byval first as integer, byval last as integer) DECLARE SUB old_get_save_slot_preview(byval slot as integer, pv as SaveSlotPreview) DECLARE SUB show_load_index(byval z as integer, caption as string, byval slot as integer=0) DECLARE SUB rebuild_inventory_captions (invent() as InventSlot) DECLARE FUNCTION old_save_slot_used (byval slot as integer) as integer DECLARE FUNCTION old_count_used_save_slots() as integer DIM SHARED old_savefile as STRING DIM SHARED savedir as STRING DIM SHARED current_save_slot as integer '--used in rsav_warn '----------------------------------------------------------------------- SUB init_save_system() '--set up old savegame file old_savefile = trimextension(sourcerpg) + ".sav" #IFDEF __UNIX__ IF NOT fileisreadable(old_savefile) THEN 'for a systemwide linux install, save files go in the prefs dir old_savefile = prefsdir & SLASH & trimpath(old_savefile) END IF #ENDIF '--set up new rsav folder DIM rpg_folder as STRING = trimfilename(sourcerpg) IF diriswriteable(rpg_folder) ANDALSO force_prefsdir_save = NO THEN '--default location is same as the RPG file (if possible) savedir = trimextension(sourcerpg) & ".saves" ELSE '--fallback location is prefsdir savedir = prefsdir & SLASH & "saves" END IF IF NOT isdir(savedir) THEN MKDIR savedir END SUB SUB loadgame (byval slot as integer) '--Works under the assumption that resetgame has already been called. IF keyval(scLeftShift) = 0 AND keyval(scRightShift) = 0 THEN 'bypass new loading if shift is held down when you load DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" IF isfile(filename) THEN debuginfo "loading awesome new loadgame from " & filename new_loadgame slot EXIT SUB END IF END IF old_loadgame slot END SUB SUB loadglobalvars (byval slot as integer, byval first as integer, byval last as integer) IF keyval(scLeftShift) = 0 AND keyval(scRightShift) = 0 THEN 'bypass new loading if shift is held down when you load DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" IF isfile(filename) THEN debuginfo "loading awesome new loadglobalvars from " & filename new_loadglobalvars slot, first, last EXIT SUB END IF END IF old_loadglobalvars slot, first, last END SUB SUB get_save_slot_preview(byval slot as integer, pv as SaveSlotPreview) IF new_save_slot_used(slot) THEN debuginfo "use nifty new save slot preview for slot " & slot new_get_save_slot_preview slot, pv ELSE debuginfo "fall back to boring old save slot preview for " & slot old_get_save_slot_preview slot, pv END IF END SUB FUNCTION save_slot_used (byval slot as integer) as integer IF new_save_slot_used(slot) THEN RETURN YES END IF RETURN old_save_slot_used(slot) END FUNCTION FUNCTION count_used_save_slots() as integer DIM count as integer = new_count_used_save_slots() IF count > 0 THEN RETURN count END IF RETURN old_count_used_save_slots() END FUNCTION '----------------------------------------------------------------------- SUB savegame (byval slot as integer) current_save_slot = slot DIM doc as DocPtr doc = CreateDocument() DIM node as NodePtr node = CreateNode(doc, "rsav") SetRootNode(doc, node) gamestate_to_reload node DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" SerializeBin filename, doc FreeDocument doc current_save_slot = -1 END SUB SUB new_loadgame(byval slot as integer) current_save_slot = slot DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" IF NOT isfile(filename) THEN debug "Save file missing: " & filename current_save_slot = -1 EXIT SUB END IF DIM doc as DocPtr doc = LoadDocument(filename) DIM node as NodePtr node = DocumentRoot(doc) gamestate_from_reload node FreeDocument doc current_save_slot = -1 END SUB SUB rsav_warn (s as STRING) debug "Save slot " & current_save_slot & ": " & s END SUB '----------------------------------------------------------------------- #warn_func = rsav_warn #error_func = rsav_warn SUB gamestate_from_reload(byval node as Reload.NodePtr) IF NodeName(node) <> "rsav" THEN rsav_warn "root node is not rsav" IF node."ver" > CURRENT_RSAV_VERSION THEN rsav_warn "new save file on old game player. Some data might get lost" fadein pop_warning "This save file was created with a more recent version of the OHRRPGCE, and some things will not load correctly. Download the latest version at http://HamsterRepublic.com" fadeout 0, 0, 0 END IF READNODE node, default node."ver".ignore node."game_client".ignore gamestate_state_from_reload node."state".warn.ptr gamestate_script_from_reload node."script".warn.ptr gamestate_maps_from_reload node."maps".warn.ptr gamestate_tags_from_reload node."tags".warn.ptr gamestate_onetime_from_reload node."onetime".warn.ptr gamestate_party_from_reload node."party".warn.ptr gamestate_inventory_from_reload node."inventory".warn.ptr gamestate_shops_from_reload node."shops".warn.ptr gamestate_vehicle_from_reload node."vehicle".warn.ptr END READNODE END SUB SUB gamestate_state_from_reload(byval node as Reload.NodePtr) gam.map.id = node."current_map".warn DIM map_offset as XYPair map_offset = load_map_pos_save_offset(gam.map.id) READNODE node, default node."current_map".ignore READNODE node."caterpillar".warn as caterpillar WITHNODE caterpillar."hero" as n DIM i as integer = GetInteger(n) SELECT CASE i CASE 0 TO 3 READNODE n, default catx(i * 5) = n."x" + map_offset.x * 20 caty(i * 5) = n."y" + map_offset.y * 20 catd(i * 5) = n."d" END READNODE CASE ELSE rsav_warn "invalid caterpillar hero index " & i END SELECT END WITHNODE END READNODE gam.random_battle_countdown = node."random_battle_countdown" READNODE node."camera".warn as ch, default mapx = ch."x" mapy = ch."y" gen(genCamera) = ch."mode" gen(genCamArg1) = ch."arg1" gen(genCamArg2) = ch."arg2" gen(genCamArg3) = ch."arg3" gen(genCamArg4) = ch."arg4" END READNODE gold = node."gold" READNODE node."playtime".warn as ch, default gen(genDays) = ch."days" gen(genHours) = ch."hours" gen(genMinutes) = ch."minutes" gen(genSeconds) = ch."seconds" END READNODE gen(genTextboxBackdrop) = node."textbox"."backdrop" READNODE node."status_char" as ch, default gen(genPoison) = ch."poison" gen(genStun) = ch."stun" gen(genMute) = ch."mute" END READNODE gen(genDamageCap) = node."damage_cap" gen(genLevelCap) = small(node."level_cap".default(99), gen(genMaxLevel)) READNODE node."stats".warn as stats WITHNODE stats."stat" as n DIM i as integer = GetInteger(n) SELECT CASE i CASE 0 TO 11 gen(genStatCap + i) = n."cap" CASE ELSE rsav_warn "invalid stat cap index " & i END SELECT END WITHNODE END READNODE END READNODE END SUB SUB gamestate_script_from_reload(byval node as Reload.NodePtr) gamestate_globals_from_reload node, 0, maxScriptGlobals DIM load_script_ids as integer = (readbit(gen(), genBits2, 2) = 0) READNODE node, default node."globals".ignore IF load_script_ids THEN gen(genGameoverScript) = script_trigger_from_reload(node."gameover_script".ptr) IF load_script_ids THEN gen(genLoadgameScript) = script_trigger_from_reload(node."loadgame_script".ptr) READNODE node."suspend".warn as ch, default setbit gen(), genSuspendBits, 0, ch."npcs".exists setbit gen(), genSuspendBits, 1, ch."player".exists setbit gen(), genSuspendBits, 2, ch."obstruction".exists setbit gen(), genSuspendBits, 3, ch."herowalls".exists setbit gen(), genSuspendBits, 4, ch."npcwalls".exists setbit gen(), genSuspendBits, 5, ch."caterpillar".exists setbit gen(), genSuspendBits, 6, ch."randomenemies".exists setbit gen(), genSuspendBits, 7, ch."boxadvance".exists setbit gen(), genSuspendBits, 8, ch."overlay".exists setbit gen(), genSuspendBits, 9, ch."ambientmusic".exists END READNODE gen(genScrBackdrop) = node."backdrop" END READNODE END SUB FUNCTION script_trigger_from_reload(byval node as NodePtr) as integer IF node = 0 THEN RETURN 0 IF node."name".exists THEN rsav_warn "still don't have code to lookup a script id by string!" 'FIXME: this doesn't work yet! RETURN 0 ELSEIF node."id".exists THEN RETURN node."id" END IF rsav_warn "neither 'id' nor 'name' found in script trigger node" RETURN 0 END FUNCTION SUB gamestate_globals_from_reload(byval parent as Reload.NodePtr, byval first as integer, byval last as integer) READNODE parent."globals".warn as node WITHNODE node."global" as n DIM i as integer i = GetInteger(n) IF i >= first AND i <= last THEN global(i) = n."int" ELSEIF i < 0 OR i > maxScriptGlobals THEN rsav_warn "invalid global id " & i END IF END WITHNODE END READNODE END SUB SUB gamestate_maps_from_reload(byval node as Reload.NodePtr) DIM i as integer DIM loaded_current as integer = NO READNODE node WITHNODE node."map" as n 'FIXME: currently only supports saving the current map i = GetInteger(n) IF i = gam.map.id THEN gamestate_npcs_from_reload n."npcs".ptr, gam.map.id loaded_current = YES END IF END WITHNODE END READNODE IF loaded_current = NO THEN rsav_warn "couldn't find saved data for current map " & gam.map.id END IF END SUB SUB gamestate_npcs_from_reload(byval node as Reload.NodePtr, byval map as integer) DIM map_offset as XYPair map_offset = load_map_pos_save_offset(map) CleanNPCL npc() DIM i as integer READNODE node WITHNODE node."npc" as n i = GetInteger(n) SELECT CASE i CASE 0 TO UBOUND(npc) load_npc_loc n, npc(i), map_offset CASE ELSE rsav_warn "invalid npc instance " & i END SELECT END WITHNODE END READNODE END SUB SUB gamestate_tags_from_reload(byval node as Reload.NodePtr) DIM count as integer count = node."count" IF count > 1000 THEN rsav_warn "too many saved tags 1000 < " & count count = 1000 END IF DIM buf(count \ 16) as integer LoadBitsetArray(node."data".ptr, buf(), UBOUND(buf)) FOR i as integer = 0 TO count - 1 settag i, readbit(buf(), 0, i) NEXT i END SUB SUB gamestate_onetime_from_reload(byval node as Reload.NodePtr) DIM count as integer count = node."count" IF count > 1032 THEN rsav_warn "too many saved tags 1032 < " & count count = 1032 END IF DIM buf(count \ 16) as integer LoadBitsetArray(node."data".ptr, buf(), UBOUND(buf)) FOR i as integer = 0 TO count - 1 settag 1000 + i, readbit(buf(), 0, i) NEXT i END SUB SUB gamestate_party_from_reload(byval node as Reload.NodePtr) DIM as integer i, j DIM her as HeroDef 'used to provide default values when missing READNODE node WITHNODE node."slot" as slot i = GetInteger(slot) SELECT CASE i CASE 0 TO 40 WITH gam.hero(i) IF slot."id".exists THEN hero(i) = slot."id" + 1 loadherodata @her, hero(i) - 1 'Load defaults (for older saves which don't contain elemental data) FOR j = 0 TO gen(genNumElements) - 1 .elementals(j) = her.elementals(j) NEXT 'Load defaults for hand position FOR j = 0 TO 1 .hand_pos(j).x = her.hand_pos(j).x .hand_pos(j).y = her.hand_pos(j).y NEXT j END IF 'Other data must be loaded even if the hero slot is empty, for compatibility READNODE slot, default slot."id".ignore names(i) = slot."name".string setbit hmask(), 0, i, slot."locked".exists READNODE slot."stats".warn as ch WITHNODE ch."stat" as n j = GetInteger(n) SELECT CASE j CASE 0 TO 11 .stat.cur.sta(j) = n."cur" .stat.max.sta(j) = n."max" CASE ELSE rsav_warn "invalid stat id " & j END SELECT END WITHNODE END READNODE .lev = slot."lev" .lev_gain = slot."lev_gain" .exp_cur = slot."exp" .exp_next = slot."exp_next" .def_wep = slot."def_wep" READNODE slot."wep".warn as ch, default .wep_pic = ch."pic" .wep_pal = ch."pal" END READNODE READNODE slot."in_battle".warn as ch, default .battle_pic = ch."pic" .battle_pal = ch."pal" END READNODE READNODE slot."walkabout".warn as ch, default .pic = ch."pic" .pal = ch."pal" END READNODE READNODE slot."hand" as hand WITHNODE hand."frame" as fr j = GetInteger(fr) .hand_pos(j).x = fr."x" .hand_pos(j).y = fr."y" END WITHNODE END READNODE ' Abandoned slot."rename_on_add".ignore slot."hide_empty_lists".ignore READNODE slot."elements" as ch ch."weak".ignore ch."strong".ignore ch."absorb".ignore WITHNODE ch."element" as n DIM j as integer = GetInteger(n) IF j < gen(genNumElements) THEN .elementals(j) = n."damage".float.default(1.0) END IF END WITHNODE END READNODE .rename_on_status = slot."rename_on_status".exists READNODE slot."battle_menus" as ch WITHNODE ch."menu" as n j = GetInteger(n) SELECT CASE j CASE 0 TO 5 IF n."attack".exists THEN bmenu(i, j) = n."attack" + 1 ELSEIF n."items".exists THEN bmenu(i, j) = -10 ELSEIF n."spells".exists THEN bmenu(i, j) = (n."spells" + 1) * -1 END IF CASE ELSE rsav_warn "invalid battle menu id " & j END SELECT END WITHNODE END READNODE READNODE slot."spell_lists".warn as ch WITHNODE ch."list" as n j = GetInteger(n) SELECT CASE j CASE 0 TO 3 gamestate_spelllist_from_reload(i, j, n) CASE ELSE rsav_warn "invalid spell list id " & j END SELECT END WITHNODE END READNODE READNODE slot."level_mp".warn as ch WITHNODE ch."lev" as n j = GetInteger(n) SELECT CASE j CASE 0 TO 7 lmp(i, j) = n."val" CASE ELSE rsav_warn "invalid level mp slot " & j END SELECT END WITHNODE END READNODE READNODE slot."equipment".warn as ch WITHNODE ch."equip" as n j = GetInteger(n) SELECT CASE j CASE 0 TO 4 eqstuf(i, j) = n."item".default(-1) + 1 CASE ELSE rsav_warn "invalid equip slot " & j END SELECT END WITHNODE END READNODE END READNODE END WITH 'gam.hero(i) CASE ELSE rsav_warn "invalid hero party slot " & i END SELECT END WITHNODE END READNODE END SUB SUB gamestate_spelllist_from_reload(byval hero_slot as integer, byval spell_list as integer, byval parent as Reload.NodePtr) DIM node as NodePtr node = parent."spells".warn.ptr IF spell_list <> GetInteger(node) THEN rsav_warn "spell list id mismatch " & spell_list & "<>" & GetInteger(node) END IF READNODE node WITHNODE node."spell" as n DIM i as integer = GetInteger(n) SELECT CASE i CASE 0 TO 23 spell(hero_slot, spell_list, i) = n."attack" + 1 CASE ELSE rsav_warn "invalid spell list slot " & i END SELECT END WITHNODE END READNODE END SUB SUB gamestate_inventory_from_reload(byval node as Reload.NodePtr) DIM i as integer gen(genMaxInventory) = node."size" IF gen(genMaxInventory) > inventoryMax THEN rsav_warn "inventory size exceeds limit: " & gen(genMaxInventory) gen(genMaxInventory) = inventoryMax END IF DIM last as integer last = last_inv_slot() READNODE node."slots".warn as ch WITHNODE ch."slot" as n i = GetInteger(n) SELECT CASE i CASE 0 TO last WITH inventory(i) .used = YES .id = n."item".warn .num = n."num".warn END WITH CASE ELSE rsav_warn "invalid inventory slot id " & i END SELECT END WITHNODE END READNODE rebuild_inventory_captions inventory() END SUB SUB gamestate_shops_from_reload(byval node as Reload.NodePtr) DIM as integer i, j DIM shoptmp(19) as integer READNODE node WITHNODE node."shop" as n i = GetInteger(n) SELECT CASE i CASE 0 TO gen(genMaxShop) loadrecord shoptmp(), game & ".sho", 20, i READNODE n."slots".warn as slots WITHNODE slots."slot" as n2 j = GetInteger(n2) SELECT CASE j CASE 0 TO shoptmp(16) gam.stock(i, j) = n2."stock".default(-1) CASE ELSE rsav_warn "invalid shop " & i & " stuff slot " & j END SELECT END WITHNODE END READNODE CASE ELSE rsav_warn "invalid shop id " & i END SELECT END WITHNODE END READNODE END SUB SUB gamestate_vehicle_from_reload(byval node as Reload.NodePtr) WITH vstate IF node."id".exists THEN READNODE node, default READNODE node."state" as ch, default .active = ch."active" .npc = ch."npc" .old_speed = ch."old_speed" .mounting = ch."mounting".exists .rising = ch."rising".exists .falling = ch."falling".exists .init_dismount = ch."init_dismount".exists .trigger_cleanup = ch."trigger_cleanup".exists .ahead = ch."ahead".exists END READNODE .id = node."id" END READNODE IF .id >= 0 THEN LoadVehicle game & ".veh", .dat, .id END IF END IF END WITH END SUB '----------------------------------------------------------------------- SUB gamestate_to_reload(byval node as Reload.NodePtr) 'increment this to produce a warning message when 'loading a new rsav file in an old game player SetChildNode(node, "ver", CURRENT_RSAV_VERSION) DIM ch as NodePtr ch = SetChildNode(node, "game_client", "OHRRPGCE") SetChildNode(ch, "branch_name", version_branch) 'version_revision is 0 if verprint could not determine it IF version_revision <> 0 THEN SetChildNode(ch, "revision", version_revision) gamestate_state_to_reload node gamestate_script_to_reload node gamestate_maps_to_reload node gamestate_tags_to_reload node gamestate_onetime_to_reload node gamestate_party_to_reload node gamestate_inventory_to_reload node gamestate_shops_to_reload node gamestate_vehicle_to_reload node END SUB SUB gamestate_state_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "state") DIM ch as NodePtr 'used for sub-containers DIM n as NodePtr 'used for numbered containers SetChildNode(node, "current_map", gam.map.id) DIM map_offset as XYPair map_offset = load_map_pos_save_offset(gam.map.id) ch = SetChildNode(node, "caterpillar") FOR i as integer = 0 TO 3 n = AppendChildNode(ch, "hero", i) SetChildNode(n, "x", catx(i * 5) - map_offset.x * 20) SetChildNode(n, "y", caty(i * 5) - map_offset.y * 20) SetChildNode(n, "d", catd(i * 5)) NEXT i SetChildNode(node, "random_battle_countdown", gam.random_battle_countdown) ch = SetChildNode(node, "camera") SetChildNode(ch, "x", mapx) SetChildNode(ch, "y", mapy) SetChildNode(ch, "mode", gen(genCamera)) FOR i as integer = 0 TO 3 SetChildNode(ch, "arg" & i+1, gen(genCamArg1 + i)) NEXT i SetChildNode(node, "gold", gold) ch = SetChildNode(node, "playtime") SetChildNode(ch, "days", gen(genDays)) SetChildNode(ch, "hours", gen(genHours)) SetChildNode(ch, "minutes", gen(genMinutes)) SetChildNode(ch, "seconds", gen(genSeconds)) ch = SetChildNode(node, "textbox") SetChildNode(ch, "backdrop", gen(genTextboxBackdrop)) ch = SetChildNode(node, "status_char") SetChildNode(ch, "poison", gen(genPoison)) SetChildNode(ch, "stun", gen(genStun)) SetChildNode(ch, "mute", gen(genMute)) SetChildNode(node, "damage_cap", gen(genDamageCap)) SetChildNode(node, "level_cap", gen(genLevelCap)) ch = SetChildNode(node, "stats") FOR i as integer = 0 TO 11 n = AppendChildNode(ch, "stat", i) SetChildNode(n, "cap", gen(genStatCap + i)) NEXT i END SUB SUB gamestate_script_to_reload(byval parent as Reload.NodePtr) 'FIXME: currently only stores a tiny bit of script state, but could store 'a lot more in the future DIM node as NodePtr node = SetChildNode(parent, "script") DIM ch as NodePtr 'used for sub-containers DIM n as NodePtr 'used for numbered containers gamestate_globals_to_reload node script_trigger_to_reload(node, "gameover_script", gen(genGameoverScript)) script_trigger_to_reload(node, "loadgame_script", gen(genLoadgameScript)) ch = SetChildNode(node, "suspend") IF readbit(gen(), genSuspendBits, 0) THEN SetChildNode(ch, "npcs") IF readbit(gen(), genSuspendBits, 1) THEN SetChildNode(ch, "player") IF readbit(gen(), genSuspendBits, 2) THEN SetChildNode(ch, "obstruction") IF readbit(gen(), genSuspendBits, 3) THEN SetChildNode(ch, "herowalls") IF readbit(gen(), genSuspendBits, 4) THEN SetChildNode(ch, "npcwalls") IF readbit(gen(), genSuspendBits, 5) THEN SetChildNode(ch, "caterpillar") IF readbit(gen(), genSuspendBits, 6) THEN SetChildNode(ch, "randomenemies") IF readbit(gen(), genSuspendBits, 7) THEN SetChildNode(ch, "boxadvance") IF readbit(gen(), genSuspendBits, 8) THEN SetChildNode(ch, "overlay") IF readbit(gen(), genSuspendBits, 9) THEN SetChildNode(ch, "ambientmusic") SetChildNode(node, "backdrop", gen(genScrBackdrop)) END SUB SUB script_trigger_to_reload(byval parent as Reload.NodePtr, node_name as STRING, byval script_id as integer) DIM node as NodePtr node = SetChildNode(parent, node_name) 'IF script_id <= 16383 THEN '--old style SetChildNode(node, "id", script_id) 'ELSE ' '--new style ' 'FIXME: this isn't saved yet because we can't load it yet ' SetChildNode(node, "name", scriptname(script_id)) 'END IF END SUB SUB gamestate_globals_to_reload(byval parent as Reload.NodePtr, byval first as integer=0, byval last as integer=maxScriptGlobals) DIM node as NodePtr node = SetChildNode(parent, "globals") DIM n as NodePtr 'used for numbered containers FOR i as integer = first TO last IF global(i) <> 0 THEN SetKeyValueNode(node, "global", i, global(i)) END IF NEXT i END SUB SUB gamestate_maps_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "maps") DIM n as NodePtr 'used for numbered containers 'FIXME: currently only supports saving the current map n = AppendChildNode(node, "map", gam.map.id) gamestate_npcs_to_reload n, gam.map.id END SUB SUB gamestate_npcs_to_reload(byval parent as Reload.NodePtr, byval map as integer) DIM node as NodePtr node = SetChildNode(parent, "npcs") DIM map_offset as XYPair map_offset = load_map_pos_save_offset(map) DIM n as NodePtr FOR i as integer = 0 TO 299 IF npc(i).id <> 0 ANDALSO NO THEN 'currently disabled for all NPCs n = AppendChildNode(node, "npc", i) save_npc_loc n, i, npc(i), map_offset END IF NEXT i END SUB SUB gamestate_tags_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "tags") DIM count as integer = 1000 SetChildNode(node, "count", count) DIM buf(INT(count / 16)) as integer FOR i as integer = 0 TO count - 1 setbit buf(), 0, i, readbit(tag(), 0, i) NEXT i DIM ch as NodePtr ch = SetChildNode(node, "data") SaveBitsetArray(ch, buf(), UBOUND(buf)) END SUB SUB gamestate_onetime_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "onetime") DIM count as integer = 1032 SetChildNode(node, "count", count) DIM buf(INT(count / 16)) as integer FOR i as integer = 0 TO count - 1 setbit buf(), 0, i, readbit(tag(), 0, 1000 + i) NEXT i DIM ch as NodePtr ch = SetChildNode(node, "data") SaveBitsetArray(ch, buf(), UBOUND(buf)) END SUB SUB gamestate_party_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "party") DIM slot as NodePtr DIM ch as NodePtr 'used for sub-containers DIM n as NodePtr 'used for numbered containers FOR i as integer = 0 TO 40 slot = AppendChildNode(node, "slot", i) IF hero(i) > 0 THEN SetChildNode(slot, "id", hero(i) - 1) SetChildNode(slot, "name", names(i)) IF xreadbit(hmask(), i) THEN SetChildNode(slot, "locked") END IF WITH gam.hero(i) ch = SetChildNode(slot, "stats") FOR j as integer = 0 TO 11 WITH .stat IF .cur.sta(j) <> 0 OR .max.sta(j) <> 0 THEN n = AppendChildNode(ch, "stat", j) SetChildNode(n, "cur", .cur.sta(j)) SetChildNode(n, "max", .max.sta(j)) END IF END WITH NEXT j SetChildNode(slot, "lev", .lev) SetChildNode(slot, "lev_gain", .lev_gain) SetChildNode(slot, "exp", .exp_cur) SetChildNode(slot, "exp_next", .exp_next) SetChildNode(slot, "def_wep", .def_wep) IF hero(i) > 0 THEN ch = SetChildNode(slot, "hand") DIM default_pos as XYPair FOR j as integer = 0 TO 1 default_pos.x = GetHeroHandPos(hero(i) - 1, j, NO) default_pos.y = GetHeroHandPos(hero(i) - 1, j, YES) IF .hand_pos(j).x <> default_pos.x ORELSE .hand_pos(j).y <> default_pos.y THEN n = AppendChildNode(ch, "frame", j) SetChildNode(n, "x", .hand_pos(j).x) SetChildNode(n, "y", .hand_pos(j).y) END IF NEXT j END IF ch = SetChildNode(slot, "wep") SetChildNode(ch, "pic", .wep_pic) SetChildNode(ch, "pal", .wep_pal) ch = SetChildNode(slot, "in_battle") SetChildNode(ch, "pic", .battle_pic) SetChildNode(ch, "pal", .battle_pal) ch = SetChildNode(slot, "walkabout") SetChildNode(ch, "pic", .pic) SetChildNode(ch, "pal", .pal) ch = SetChildNode(slot, "battle_menus") FOR j as integer = 0 TO 5 n = AppendChildNode(ch, "menu", j) SELECT CASE bmenu(i, j) CASE IS > 0: '--attack from weapon SetChildNode(n, "attack", bmenu(i, j) - 1) CASE -10: '--items menu SetChildNode(n, "items") CASE -4 TO -1: '--spell list SetChildNode(n, "spells", ABS(bmenu(i, j)) - 1) END SELECT NEXT j ch = SetChildNode(slot, "spell_lists") FOR j as integer = 0 TO 3 n = AppendChildNode(ch, "list", j) gamestate_spelllist_to_reload(i, j, n) NEXT j ch = SetChildNode(slot, "level_mp") FOR j as integer = 0 TO 7 IF lmp(i, j) <> 0 THEN SetKeyValueNode(ch, "lev", j, lmp(i, j), "val") END IF NEXT j ch = SetChildNode(slot, "equipment") FOR j as integer = 0 TO 4 IF eqstuf(i, j) > 0 THEN SetKeyValueNode(ch, "equip", j, eqstuf(i, j) - 1, "item") END IF NEXT j IF hero(i) THEN 'Unlike all the other hero commands, hero elemental commands aren't allowed 'to read/write empty hero slots, so don't need to save those ch = SetChildNode(slot, "elements") FOR j as integer = 0 TO gen(genNumElements) - 1 n = AppendChildNode(ch, "element", j) SetChildNode(n, "damage", cast(double, .elementals(j))) NEXT j IF .rename_on_status THEN SetChildNode(slot, "rename_on_status") END IF END WITH NEXT i END SUB SUB gamestate_spelllist_to_reload(byval hero_slot as integer, byval spell_list as integer, byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "spells", spell_list) DIM n as NodePtr 'used for numbered containers DIM atk_id as integer FOR i as integer = 0 TO 23 atk_id = spell(hero_slot, spell_list, i) - 1 IF atk_id >= 0 THEN n = AppendChildNode(node, "spell", i) SetChildNode(n, "attack", atk_id) END IF NEXT i END SUB SUB gamestate_inventory_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "inventory") DIM ch as NodePtr 'used for sub-containers DIM n as NodePtr 'used for numbered containers SetChildNode(node, "size", gen(genMaxInventory)) ch = SetChildNode(node, "slots") DIM last as integer last = small(inventoryMax, UBOUND(inventory)) FOR i as integer = 0 TO last WITH inventory(i) IF .used THEN n = AppendChildNode(ch, "slot", i) SetChildNode(n, "item", .id) SetChildNode(n, "num", .num) END IF END WITH NEXT i END SUB SUB gamestate_shops_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "shops") DIM ch as NodePtr 'used for sub-containers DIM n as NodePtr 'used for numbered containers DIM n2 as NodePtr 'also used for numbered containers DIM shoptmp(19) as integer FOR i as integer = 0 TO gen(genMaxShop) n = AppendChildNode(node, "shop", i) loadrecord shoptmp(), game & ".sho", 20, i ch = SetChildNode(n, "slots") FOR j as integer = 0 TO shoptmp(16) IF gam.stock(i, j) >= 0 THEN n2 = AppendChildNode(ch, "slot", j) SetChildNode(n2, "stock", gam.stock(i, j)) END IF NEXT j NEXT i END SUB SUB gamestate_vehicle_to_reload(byval parent as Reload.NodePtr) DIM node as NodePtr node = SetChildNode(parent, "vehicle") DIM ch as NodePtr 'used for sub-containers DIM n as NodePtr 'used for numbered containers WITH vstate IF .id >= 0 THEN SetChildNode(node, "id", .id) ch = SetChildNode(node, "state") SetChildNode(ch, "active", .active) SetChildNode(ch, "npc", .npc) SetChildNode(ch, "old_speed", .old_speed) IF .mounting THEN SetChildNode(ch, "mounting") IF .rising THEN SetChildNode(ch, "rising") IF .falling THEN SetChildNode(ch, "falling") IF .init_dismount THEN SetChildNode(ch, "init_dismount") IF .trigger_cleanup THEN SetChildNode(ch, "trigger_cleanup") IF .ahead THEN SetChildNode(ch, "ahead") END IF END WITH END SUB '----------------------------------------------------------------------- SUB saveglobalvars (byval slot as integer, byval first as integer, byval last as integer) current_save_slot = slot DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" DIM doc as DocPtr DIM rsav_node as NodePtr DIM script_node as NodePtr DIM globals_node as NodePtr IF NOT isfile(filename) THEN debuginfo "Save file missing: " & filename debuginfo "generating a globals-only file" doc = CreateDocument() rsav_node = CreateNode(doc, "rsav") SetRootNode(doc, rsav_node) script_node = SetChildNode(rsav_node, "script") globals_node = SetChildNode(script_node, "globals") ELSE '--save already exists doc = LoadDocument(filename) '--get the old globals node rsav_node = DocumentRoot(doc) script_node = rsav_node."script".ptr globals_node = script_node."globals".ptr END IF DIM n as NodePtr DIM nextch as NodePtr '--delete any old global nodes in the range n = FirstChild(globals_node, "global") DO WHILE n nextch = NextSibling(n, "global") SELECT CASE GetInteger(n) CASE first TO last FreeNode(n) END SELECT n = nextch LOOP '--add nodes for the globals in the range gamestate_globals_to_reload script_node, first, last '--re-save with the changed globals SerializeBin filename, doc FreeDocument doc current_save_slot = -1 END SUB SUB new_loadglobalvars (byval slot as integer, byval first as integer, byval last as integer) current_save_slot = slot DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" IF NOT isfile(filename) THEN debug "Save file missing: " & filename current_save_slot = -1 EXIT SUB END IF DIM doc as DocPtr doc = LoadDocument(filename) DIM root as NodePtr root = DocumentRoot(doc) '--wipe out the range of globals first FOR i as integer = first TO last global(i) = 0 NEXT i '--load the globals in the range gamestate_globals_from_reload root."script".ptr, first, last FreeDocument doc current_save_slot = -1 END SUB '----------------------------------------------------------------------- SUB erase_save_slot (byval slot as integer) DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" safekill filename END SUB FUNCTION new_save_slot_used (byval slot as integer) as integer DIM result as integer = NO DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" IF isfile(filename) THEN DIM doc as DocPtr doc = LoadDocument(filename) DIM node as NodePtr node = DocumentRoot(doc) IF node."ver".exists THEN result = YES END IF FreeDocument doc END IF RETURN result END FUNCTION FUNCTION new_count_used_save_slots() as integer DIM count as integer = 0 FOR slot as integer = 0 TO 32 IF new_save_slot_used(slot) THEN count += 1 NEXT slot RETURN count END FUNCTION '----------------------------------------------------------------------- SUB new_get_save_slot_preview(byval slot as integer, pv as SaveSlotPreview) current_save_slot = slot pv.valid = NO DIM filename as STRING filename = savedir & SLASH & slot & ".rsav" IF NOT isfile(filename) THEN current_save_slot = -1 EXIT SUB END IF DIM doc as DocPtr doc = LoadDocument(filename) DIM parent as NodePtr parent = DocumentRoot(doc) '--if there is no version data, don't continue ' (this could happen if export globals was used into an empty slot) IF parent."ver".exists = NO THEN FreeDocument doc current_save_slot = -1 EXIT SUB END IF '--Loaded data okay! populate the SaveSlotPreview object pv.valid = YES pv.cur_map = parent."state"."current_map".warn DIM h as NodePtr DIM stat as NodePtr DIM foundleader as integer = NO FOR i as integer = 0 TO 3 pv.hero_id(i) = 0 h = NodeByPath(parent, "/party/slot[" & i & "]") IF h THEN IF h."id".exists THEN pv.hero_id(i) = h."id" + 1 IF foundleader = NO THEN pv.leader_name = h."name".string pv.leader_lev = h."lev" foundleader = YES END IF WITH pv.hero(i) .lev = h."lev" FOR j as integer = 0 TO 11 stat = NodeByPath(h, "/stats/stat[" & j & "]") IF stat THEN .stat.cur.sta(j) = stat."cur" .stat.max.sta(j) = stat."max" END IF NEXT j .def_wep = h."def_wep" .battle_pic = h."in_battle"."pic" .battle_pal = h."in_battle"."pal" .pic = h."walkabout"."pic" .pal = h."walkabout"."pal" END WITH END IF END IF NEXT i DIM ch as NodePtr ch = parent."state"."playtime".ptr pv.playtime = playtime(ch."days", ch."hours", ch."minutes") FreeDocument doc current_save_slot = -1 END SUB '----------------------------------------------------------------------- '======================================================================= '----------------------------------------------------------------------- SUB old_loadgame (byval slot as integer) DIM gmaptmp(dimbinsize(binMAP)) as integer DIM as integer i, j, o, z '--return gen to defaults xbload game + ".gen", gen(), "General data is missing from " + sourcerpg DIM sg as STRING = old_savefile setpicstuf buffer(), 30000, -1 loadset sg, slot * 2, 0 DIM savver as integer = buffer(0) IF savver < 2 OR savver > 3 THEN EXIT SUB debuginfo "loading from slot " & slot & " of boring old " & old_savefile gam.map.id = buffer(1) loadrecord gmaptmp(), game + ".map", getbinsize(binMAP) \ 2, gam.map.id catx(0) = buffer(2) + gmaptmp(20) * 20 caty(0) = buffer(3) + gmaptmp(21) * 20 catd(0) = buffer(4) gam.random_battle_countdown = buffer(5) 'leader = buffer(6) mapx = buffer(7) mapy = buffer(8) DIM gold_str as STRING = "" FOR i = 0 TO 24 IF buffer(i + 9) < 0 OR buffer(i + 9) > 255 THEN buffer(i + 9) = 0 IF buffer(i + 9) > 0 THEN gold_str &= CHR(buffer(i + 9)) NEXT i gold = str2int(gold_str) z = 34 show_load_index z, "gen" FOR i = 0 TO 500 SELECT CASE i 'Only certain gen() values should be read from the saved game. 'See http://rpg.HamsterRepublic.com/ohrrpgce/GEN CASE 42, 57 'genGameoverScript and genLoadgameScript IF readbit(gen(), genBits2, 2) = 0 THEN gen(i) = buffer(z) END IF CASE 44 TO 54, 58, 60 TO 76, 85 gen(i) = buffer(z) END SELECT z = z + 1 NEXT i show_load_index z, "npcl" DeserNPCL npc(),z,buffer(),300,gmaptmp(20),gmaptmp(21) show_load_index z, "unused" z=z+1 'fix an old bug show_load_index z, "tags" FOR i = 0 TO 126 tag(i) = buffer(z): z = z + 1 NEXT i show_load_index z, "heroes" FOR i = 0 TO 40 hero(i) = buffer(z): z = z + 1 NEXT i show_load_index z, "unused a" FOR i = 0 TO 500 '--used to be the useless a() buffer z = z + 1 NEXT i show_load_index z, "stats" FOR i = 0 TO 40 FOR j = 0 TO 11 gam.hero(i).stat.cur.sta(j) = buffer(z): z = z + 1 NEXT j gam.hero(i).lev = buffer(z): z = z + 1 gam.hero(i).wep_pic = buffer(z): z = z + 1 FOR j = 0 TO 11 gam.hero(i).stat.max.sta(j) = buffer(z): z = z + 1 NEXT j gam.hero(i).lev_gain = buffer(z): z = z + 1 gam.hero(i).wep_pal = buffer(z): z = z + 1 NEXT i show_load_index z, "bmenu" FOR i = 0 TO 40 FOR o = 0 TO 5 bmenu(i, o) = buffer(z): z = z + 1 NEXT o NEXT i show_load_index z, "spell" FOR i = 0 TO 40 FOR o = 0 TO 3 FOR j = 0 TO 23 spell(i, o, j) = buffer(z): z = z + 1 NEXT j z = z + 1'--skip extra data NEXT o NEXT i show_load_index z, "lmp" FOR i = 0 TO 40 FOR o = 0 TO 7 lmp(i, o) = buffer(z): z = z + 1 NEXT o NEXT i show_load_index z, "exlev" DIM exp_str as STRING FOR i = 0 TO 40 FOR o = 0 TO 1 exp_str = "" FOR j = 0 TO 25 IF buffer(z) < 0 OR buffer(z) > 255 THEN buffer(z) = 0 IF buffer(z) > 0 THEN exp_str &= CHR(buffer(z)) z = z + 1 NEXT j IF o = 0 THEN gam.hero(i).exp_cur = str2int(exp_str) IF o = 1 THEN gam.hero(i).exp_next = str2int(exp_str) NEXT o NEXT i show_load_index z, "names" FOR i = 0 TO 40 names(i) = "" FOR j = 0 TO 16 IF buffer(z) < 0 OR buffer(z) > 255 THEN buffer(z) = 0 IF buffer(z) > 0 THEN names(i) &= CHR(buffer(z)) z = z + 1 NEXT j NEXT i show_load_index z, "inv_mode" DIM inv_mode as integer inv_mode = buffer(z) show_load_index z, "inv 8bit" IF inv_mode = 0 THEN ' Read 8-bit inventory data from old SAV files DeserInventory8Bit inventory(), z, buffer() ELSE 'Skip this section z = 14595 END IF show_load_index z, "eqstuff" FOR i = 0 TO 40 FOR o = 0 TO 4 eqstuf(i, o) = buffer(z): z = z + 1 NEXT o NEXT i show_load_index z, "inv 16bit" IF inv_mode = 1 THEN ' Read 16-bit inventory data from newer SAV files LoadInventory16Bit inventory(), z, buffer(), 0, 99 END IF show_load_index z, "after inv 16bit" 'RECORD 2 setpicstuf buffer(), 30000, -1 loadset sg, slot * 2 + 1, 0 z = 0 show_load_index z, "stock", 1 FOR i = 0 TO 99 FOR o = 0 TO 49 gam.stock(i, o) = buffer(z): z = z + 1 NEXT o NEXT i show_load_index z, "hmask", 1 FOR i = 0 TO 3 IF i <= UBOUND(hmask) THEN hmask(i) = buffer(z) END IF z = z + 1 NEXT i show_load_index z, "cathero", 1 FOR i = 1 TO 3 catx(i * 5) = buffer(z) + gmaptmp(20) * 20: z = z + 1 caty(i * 5) = buffer(z) + gmaptmp(21) * 20: z = z + 1 catd(i * 5) = buffer(z): z = z + 1 NEXT i show_load_index z, "globals low", 1 FOR i = 0 TO 1024 global(i) = (buffer(z) AND &hFFFF): z = z + 1 NEXT i show_load_index z, "vstate", 1 WITH vstate .active = buffer(z+0) <> 0 .npc = buffer(z+5) .id = -1 ' We set .id by looking at .npc's definition. But we can't do that here, npc() isn't loaded until preparemap .mounting = xreadbit(buffer(), 0, z+6) .rising = xreadbit(buffer(), 1, z+6) .falling = xreadbit(buffer(), 2, z+6) .init_dismount = xreadbit(buffer(), 3, z+6) .trigger_cleanup = xreadbit(buffer(), 4, z+6) .ahead = xreadbit(buffer(), 5, z+6) .old_speed = buffer(z+7) WITH .dat .speed = buffer(z+8) .pass_walls = xreadbit(buffer(), 0, z+9) .pass_npcs = xreadbit(buffer(), 1, z+9) .enable_npc_activation = xreadbit(buffer(), 2, z+9) .enable_door_use = xreadbit(buffer(), 3, z+9) .do_not_hide_leader = xreadbit(buffer(), 4, z+9) .do_not_hide_party = xreadbit(buffer(), 5, z+9) .dismount_ahead = xreadbit(buffer(), 6, z+9) .pass_walls_while_dismounting = xreadbit(buffer(), 7, z+9) .disable_flying_shadow = xreadbit(buffer(), 8, z+9) .random_battles = buffer(z+11) .use_button = buffer(z+12) .menu_button = buffer(z+13) .riding_tag = buffer(z+14) .on_mount = buffer(z+15) .on_dismount = buffer(z+16) .override_walls = buffer(z+17) .blocked_by = buffer(z+18) .mount_from = buffer(z+19) .dismount_to = buffer(z+20) .elevation = buffer(z+21) END WITH END WITH z += 22 '--picture and palette show_load_index z, "picpal magic", 1 DIM picpalmagicnum as integer = buffer(z): z = z + 1 show_load_index z, "picpalwep", 1 FOR i = 0 TO 40 IF picpalmagicnum = 4444 THEN gam.hero(i).battle_pic = buffer(z) gam.hero(i).battle_pal = buffer(z+1) gam.hero(i).def_wep = buffer(z+2) gam.hero(i).pic = buffer(z+3) gam.hero(i).pal = buffer(z+4) END IF z = z + 6 NEXT i 'native hero bitsets show_load_index z, "hbit magic", 1 DIM nativebitmagicnum as integer = buffer(z): z = z + 1 show_load_index z, "hbits", 1 'Just totally ignore all the hero bits, as none of them are/were modifiable anyway; 'nativehbits() was removed z += 205 'top global variable bits show_load_index z, "global high", 1 FOR i = 0 TO 1024 global(i) or= buffer(z) shl 16: z = z + 1 NEXT i show_load_index z, "global ext", 1 FOR i = 1025 TO 4095 'maxScriptGlobals const is NOT appropriate here global(i) = buffer(z) and &hFFFF: z = z + 1 global(i) or= buffer(z) shl 16: z = z + 1 NEXT i show_load_index z, "inv 16bit ext", 1 IF inv_mode = 1 THEN ' Read 16-bit inventory data from newer SAV files IF inventoryMax <> 599 THEN debug "Warning: inventoryMax=" & inventoryMax & ", does not fit in old SAV format" LoadInventory16Bit inventory(), z, buffer(), 100, 599 ELSE 'skip this section for old saves z = 29680 - 15000 END IF show_load_index z, "unused", 1 rebuild_inventory_captions inventory() '---BLOODY BACKWARD COMPATABILITY--- 'fix doors... 'James guesses that "fix doors" is a thing that we need to do, but are not doing yet? IF savver = 2 THEN gen(genVersion) = 3 'Here we load hero state that is currently in RSAV files but that was 'not stored in the old .SAV format DIM her as HeroDef FOR i = 0 TO 40 IF hero(i) > 0 THEN loadherodata @her, hero(i) - 1 gam.hero(i).rename_on_status = readbit(her.bits(), 0, 25) '--fix appearance settings IF picpalmagicnum <> 4444 THEN gam.hero(i).battle_pic = her.sprite gam.hero(i).battle_pal = her.sprite_pal gam.hero(i).pic = her.walk_sprite gam.hero(i).pal = her.walk_sprite_pal gam.hero(i).def_wep = her.def_weapon + 1'default weapon END IF FOR j = 0 TO gen(genNumElements) - 1 gam.hero(i).elementals(j) = her.elementals(j) NEXT FOR j = 0 TO 1 gam.hero(i).hand_pos(j).x = her.hand_pos(j).x gam.hero(i).hand_pos(j).y = her.hand_pos(j).y NEXT j END IF NEXT i 'See http://rpg.hamsterrepublic.com/ohrrpgce/SAV for docs END SUB SUB old_loadglobalvars (byval slot as integer, byval first as integer, byval last as integer) DIM i as integer DIM buf((last - first + 1) * 2) as integer = ANY IF isfile(old_savefile) THEN debuginfo "loadglobalvars from slot " & slot & " of boring old " & old_savefile DIM fh as integer = FREEFILE OPEN old_savefile FOR BINARY as #fh IF first <= 1024 THEN 'grab first-final DIM final as integer = small(1024, last) SEEK #fh, 60000 * slot + 2 * first + 40027 '20013 * 2 + 1 loadrecord buf(), fh, final - first + 1, -1 FOR i = 0 TO final - first global(first + i) = buf(i) and &hFFFF NEXT SEEK #fh, 60000 * slot + 2 * first + 43027 '21513 * 2 + 1 loadrecord buf(), fh, final - first + 1, -1 FOR i = 0 TO final - first global(first + i) or= buf(i) shl 16 NEXT END IF IF last >= 1025 THEN 'grab start-last DIM start as integer = large(1025, first) SEEK #fh, 60000 * slot + 4 * (start - 1025) + 45077 '22538 * 2 + 1 loadrecord buf(), fh, (last - start + 1) * 2, -1 FOR i = 0 TO last - start global(start + i) = buf(i * 2) and &hFFFF global(start + i) or= buf(i * 2 + 1) shl 16 NEXT END IF CLOSE #fh ELSE FOR i = first TO last global(i) = 0 NEXT END IF END SUB SUB show_load_index(byval z as integer, caption as string, byval slot as integer=0) 'debug "SAV:" & LEFT(caption & STRING(20, " "), 20) & " int=" & z + slot * 15000 END SUB SUB rebuild_inventory_captions (invent() as InventSlot) DIM i as integer FOR i = 0 TO inventoryMax update_inventory_caption i NEXT i END SUB SUB old_get_save_slot_preview(byval slot as integer, pv as SaveSlotPreview) setpicstuf buffer(), 30000, -1 loadset old_savefile, slot * 2, 0 IF buffer(0) <> 3 THEN '--currently only understands v3 binary sav format pv.valid = NO EXIT SUB END IF pv.valid = YES pv.cur_map = buffer(1) '-get stats DIM z as integer = 3305 FOR i as integer = 0 TO 3 FOR j as integer = 0 TO 11 pv.hero(i).stat.cur.sta(j) = buffer(z): z += 1 NEXT j pv.hero(i).lev = buffer(z): z += 1 z += 1 'skip weapon pic because we could care less right now FOR j as integer = 0 TO 11 pv.hero(i).stat.max.sta(j) = buffer(z): z += 1 NEXT j NEXT i '--get play time z = 34 + 51 pv.playtime = playtime(buffer(z), buffer(z + 1), buffer(z + 2)) '--leader data DIM foundleader as integer = NO pv.leader_name = "" FOR o as integer = 0 TO 3 '--load hero ID pv.hero_id(o) = buffer(2763 + o) '--leader name and level IF foundleader = NO AND pv.hero_id(o) > 0 THEN foundleader = YES FOR j as integer = 0 TO 15 DIM k as integer = buffer(11259 + (o * 17) + j) IF k > 0 AND k < 255 THEN pv.leader_name &= CHR(k) NEXT j pv.leader_lev = pv.hero(o).lev END IF NEXT o '--load second record loadset old_savefile, slot * 2 + 1, 0 z = 6060 DIM use_saved_pics as integer = NO IF buffer(z) = 4444 THEN use_saved_pics = YES z += 1 FOR i as integer = 0 TO 3 IF use_saved_pics THEN pv.hero(i).battle_pic = buffer(z) pv.hero(i).battle_pal = buffer(z+1) pv.hero(i).def_wep = buffer(z+2) pv.hero(i).pic = buffer(z+3) pv.hero(i).pal = buffer(z+4) z += 6 ELSE '--backcompat (for ancient SAV files) IF pv.hero_id(i) > 0 THEN DIM her as HeroDef loadherodata @her, pv.hero_id(i) - 1 pv.hero(i).battle_pic = her.sprite pv.hero(i).battle_pal = her.sprite_pal pv.hero(i).pic = her.walk_sprite pv.hero(i).pal = her.walk_sprite_pal pv.hero(i).def_wep = her.def_weapon + 1 END IF END IF NEXT i END SUB FUNCTION old_save_slot_used (byval slot as integer) as integer IF isfile(old_savefile) = 0 THEN RETURN NO DIM as SHORT saveversion DIM savh as integer = FREEFILE OPEN old_savefile FOR BINARY as #savh GET #savh, 1 + 60000 * slot, saveversion CLOSE #savh RETURN (saveversion = 3) END FUNCTION SUB old_erase_save_slot (byval slot as integer) DIM as SHORT saveversion = 0 IF fileisreadable(old_savefile) = NO THEN EXIT SUB DIM savh as integer = FREEFILE OPEN old_savefile FOR BINARY as #savh IF LOF(savh) > 60000 * slot THEN PUT #savh, 1 + 60000 * slot, saveversion END IF CLOSE #savh END SUB FUNCTION old_count_used_save_slots() as integer DIM i as integer DIM n as integer DIM savver as integer n = 0 setpicstuf buffer(), 30000, -1 FOR i = 0 TO 3 loadset old_savefile, i * 2, 0 savver = buffer(0) IF savver = 3 THEN n += 1 NEXT i RETURN n END FUNCTION