diff --git a/artifacts/config_files/Perks.ini b/artifacts/config_files/Perks.ini index 9c951297..c20e1dc7 100644 --- a/artifacts/config_files/Perks.ini +++ b/artifacts/config_files/Perks.ini @@ -27,6 +27,10 @@ WeaponAccurateBonus=20 WeaponHandlingBonus=3 ;############################################################################## +[Perks] +;Set to 1 to enable the modifications for perks +Enable=0 + ;Name=The name of the perk (max 63 characters) ;Desc=The description of the perk (max 255 characters) ;Image=The line number (0-indexed) of the corresponding FRM in skilldex.lst @@ -119,6 +123,11 @@ Skill4Mod=0 Skill5=-1 Skill5Mod=0 +;############################################################################## +[Traits] +;Set to 1 to enable the modifications for traits +Enable=0 + ;This is a modification to trait 0 [t0] NoHardcode=0 diff --git a/artifacts/config_files/npcarmor - vanilla.ini b/artifacts/config_files/npcarmor - vanilla.ini new file mode 100644 index 00000000..188d171a --- /dev/null +++ b/artifacts/config_files/npcarmor - vanilla.ini @@ -0,0 +1,82 @@ +;NPC armor appearance configuration for vanilla Fallout 2. Does not require any new graphics +;Party members will use player's armor appearance just like old B-Team mod + +;WeaponAnims codes +; 1 - Knife (D) +; 2 - Club (E) +; 3 - Sledgehammer (F) +; 4 - Spear (G) +; 5 - Pistol (H) +; 6 - SMG (I) +; 7 - Rifle (J) +; 8 - Big Gun (K) +; 9 - Minigun (L) +; 10 - Rocket Launcher (M) +; 11-15 - sfall additional weapon animation codes + +; This section maps 7 armor types to corresponding armor item PIDs +[ArmorTypes] +Jacket = 74,265 +Leather = 1,379 +Metal = 2,240,380 +Power = 3,232 +AdvPower = 348,349 +Combat = 17,239,381 +Robe = 113,524 + +; Default armor FIDs (the same for all NPCs) +[Default] +Jacket = 16777229 +Leather = 16777228 +Metal = 16777230 +Combat = 16777226 +Power = 16777217 +AdvPower = 16777287 +Robe = 16777218 + +; Sulik +[1] +PID=16777313 +WeaponAnims=1,3,4,6 +Default=16777280 + +; Vic +[2] +PID=16777278 +WeaponAnims=1,5,7 +Default=16777307 + +; Cassidy +[3] +PID=16777305 +WeaponAnims=4,5,7 +Default=16777260 +Leather=16777260 + +; Myron +[4] +PID=16777376 +WeaponAnims=1,5 +Default=16777304 + +; Lenny +[5] +PID=16777323 +WeaponAnims=1,5,6 +Default=16777295 + +; Davin +[6] +PID=16777379 +WeaponAnims=1,2,5 +Default=16777264 + +; Miria +[7] +PID=16777380 +WeaponAnims=1,6 +Default=16777252 +Jacket=16777222 +Leather=16777221 +Metal=16777223 +Combat=16777219 diff --git a/artifacts/config_files/npcarmor.ini b/artifacts/config_files/npcarmor.ini index 65572995..4b38cd15 100644 --- a/artifacts/config_files/npcarmor.ini +++ b/artifacts/config_files/npcarmor.ini @@ -23,69 +23,67 @@ Robe = 113,524 ; Default armor FIDs (the same for all NPCs) [Default] -Power=16777217 -AdvPower=16777287 -Robe=16777218 - +Power = 16777217 +AdvPower = 16777287 +Robe = 16777218 ; Sulik [1] PID=16777313 -WeaponAnims=1,3,4,6 +WeaponAnims=1,3,4,6,14 Default=16777280 -Leather=16777325 -Power=16777324 -Metal=16777323 Jacket=16777321 +Leather=16777325 +Metal=16777323 Combat=16777322 +Power=16777324 ; Vic [2] PID=16777278 -WeaponAnims=1,5,7 +WeaponAnims=1,5,7,13,14 Default=16777307 Jacket=16777329 -Combat=16777330 -Metal=16777331 -Power=16777332 Leather=16777333 +Metal=16777331 +Combat=16777330 +Power=16777332 ; Cassidy [3] PID=16777305 -WeaponAnims=4,5,7 +WeaponAnims=4,5,7,13 Default=16777354 -Leather=16777260 -Power=16777328 -Metal=16777327 Jacket=16777351 +Leather=16777260 +Metal=16777327 Combat=16777326 +Power=16777328 ; Myron [4] PID=16777376 -WeaponAnims=1,5 +WeaponAnims=1,5,14 Default=16777304 -Leather= -Power=16777349 Combat=16777350 +Power=16777349 -; Cat Jules +; Cat Jules (RP) [5] -PID=16777734 +PID=16777720 WeaponAnims=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 Default=16777353 +Jacket=16777346 Leather=16777347 Metal=16777348 -Jacket=16777346 Combat=16777226 -; Kitsune +; Kitsune (RP) [6] -PID=16777724 +PID=16777718 WeaponAnims=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 Default=16777222 +Jacket=16777222 Leather=16777221 Metal=16777223 -Jacket=16777222 Combat=16777219 diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index ca0b6827..0decda1d 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -1,5 +1,5 @@ ;sfall configuration settings -;v4.2 +;v4.2.1 [Main] ;Change to 1 if you want to use command line args to tell sfall to use another ini file. @@ -94,7 +94,7 @@ GPUBlt=0 Use32BitHeadGraphics=0 ;Set to 1 to automatically search for alternative avi video files when Fallout tries to play the game movies -;Requires graphics mode 4 or 5 +;Requires DX9 graphics mode 4 or 5 AllowDShowMovies=0 ;Fade effect time percentage modifier @@ -186,7 +186,7 @@ FastMoveFromContainer=0 ;A key to press to open a debug game editor ;Set to 0 to disable, or a DX scancode otherwise -;Requires sfall debugging mode to be enabled and FalloutClient.exe from the modders pack +;Requires sfall debugging mode and FalloutClient.exe from the modders pack DebugEditorKey=0 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -384,9 +384,6 @@ AlwaysReloadMsgs=0 ;Set to 1 to force the player to play the idle animation when reloading their weapon PlayIdleAnimOnReload=0 -;Set to 1 to prevent corpses from blocking line of fire -CorpseLineOfFireFix=1 - ;Changes the timer (in days) for deleting corpses on a map after you leave (valid range: 0..13) ;The corpses of critters with 'Ages' flag or on maps with 'dead_bodies_age=No' set in maps.txt will not disappear ;Default is 6. Set to 0 for a 12-hour timer @@ -399,7 +396,7 @@ CorpseDeleteTime=6 ProcessorIdle=-1 ;Set to 1 if using the hero appearance mod -;Set to 2 for backward compatibility with old scripts that manually fix obj_art_fid script function for dude_obj +;Set to 2 for backward compatibility with scripts that manually fix obj_art_fid/art_change_fid_num script functions for dude_obj ;You can add AppChCrt.frm and AppChEdt.frm files to art\intrface\ to set a custom background for the character screen EnableHeroAppearanceMod=0 @@ -412,8 +409,9 @@ SkipOpeningMovies=0 ;Set to 0 to disable NPCsTryToSpendExtraAP=0 -;Set to 1 to fix NPCs not checking weapon perks properly when searching for the best weapon -;Note that enabling this option can significantly affect the weapon choice of some NPCs in combat +;Set to 1 to fix NPCs not checking weapon perks properly when choosing the best weapon in combat +;Set to 2 to change the priority multiplier for having weapon perk to 3x (the original is 5x) +;Note that enabling this option can significantly affect the weapon of choice for some NPCs AIBestWeaponFix=0 ;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as drug use preference when using drugs in their inventory @@ -448,6 +446,7 @@ UseScrollingQuestsList=1 ;Uncomment these lines to control the premade characters offered when starting a new game ;Multiple options should be separated by commas, and there must be the same number of entries in both lines +;Each name in PremadePaths is limited to 11 characters ;PremadePaths=combat,diplomat,stealth ;PremadeFIDs=201,203,202 @@ -508,9 +507,6 @@ AnimationsAtOnceLimit=120 ;Set to 1 to remove the limits that stop the player rolling critical successes/misses in the first few days of game time RemoveCriticalTimelimits=0 -;Set to 1 to enable party members with level 6 protos to reach level 6 -NPCStage6Fix=0 - ;Change the colour of the font used on the main menu for the Fallout/sfall version number and copyright text ;It's the last byte ('3C' by default) that picks the colour used. The first byte supplies additional flags for this option ;MainMenuFontColour=0x00003C @@ -646,7 +642,7 @@ StackEmptyWeapons=0 ;If the amount of ammo boxes in the inventory is less than or equal to the reserve, only one box will be used ReloadReserve=-1 -;Set to 1 to change the counter in the 'Move Items' window to start with maximum number except in the barter screen +;Set to 1 to change the counter in the 'Move Items' window to start with maximum number, except in the barter screen ItemCounterDefaultMax=0 ;Set to 1 to leave the music playing in dialogue with talking heads @@ -748,8 +744,8 @@ GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int Enable=0 ;Fallout 2 Debug Patch -;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and a debug.log file -;Does not require enabling sfall debugging mode +;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and debug.log +;Does not require sfall debugging mode ;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg: ;------- ;[debug] @@ -765,19 +761,18 @@ Enable=0 DebugMode=0 ;Set to 1 to hide error messages in debug output when a null value is passed to the function as an object -;Requires DebugMode to be enabled HideObjIsNullMsg=0 ;Change to 1 to skip the compatibility mode check SkipCompatModeCheck=0 ;Set to 1 to skip the executable file size check -;Does not require enabling sfall debugging mode +;Does not require sfall debugging mode SkipSizeCheck=0 ;If you're testing changes to the Fallout exe, you can override the CRC that sfall looks for here ;You can use several hex values, separated by commas -;Does not require enabling sfall debugging mode +;Does not require sfall debugging mode ;ExtraCRC=0x00000000,0x00000000 ;Set to 1 to stop Fallout from deleting non read-only protos at startup @@ -785,20 +780,20 @@ SkipSizeCheck=0 DontDeleteProtos=0 ;Set to 1 to give scripts direct access to Fallout's address space, and to make arbitrary calls into Fallout's code +;Does not require sfall debugging mode AllowUnsafeScripting=0 -;Set to 1 to force sfall to search for global scripts every time the game loads rather than only once on the first game start -AlwaysFindScripts=0 - ;Set to 1 to force sfall to inject all hooks code into the game, even if corresponding hook scripts don't exist InjectAllGameHooks=0 +;Set to 1 to force sfall to search for global scripts every time the game loads rather than only once on the first game start +AlwaysFindScripts=0 + ;Set to 1 to force critters to display combat float messages ;Requires AllowSoundForFloats to be enabled Test_ForceFloats=0 ;These options control what output is saved in the debug log (sfall-log.txt) - ;Prints messages duing sfall initialization Init=0 ;Prints messages relating to hook scripts diff --git a/artifacts/example_mods/UIHotkeys/gl_uihotkeys.int b/artifacts/example_mods/UIHotkeys/gl_uihotkeys.int index 492d08a2..7b0de54c 100644 Binary files a/artifacts/example_mods/UIHotkeys/gl_uihotkeys.int and b/artifacts/example_mods/UIHotkeys/gl_uihotkeys.int differ diff --git a/artifacts/example_mods/UIHotkeys/gl_uihotkeys.ssl b/artifacts/example_mods/UIHotkeys/gl_uihotkeys.ssl index 8600570f..aff556fd 100644 --- a/artifacts/example_mods/UIHotkeys/gl_uihotkeys.ssl +++ b/artifacts/example_mods/UIHotkeys/gl_uihotkeys.ssl @@ -45,6 +45,7 @@ procedure start begin tap_key(DIK_A); tap_key(DIK_CAPITAL); end else if (mode bwand BARTER) then begin + play_sfx("IB2P1XX1"); tap_key(DIK_M); end end diff --git a/artifacts/mods/gl_highlighting.int b/artifacts/mods/gl_highlighting.int index 3112fa38..fca11798 100644 Binary files a/artifacts/mods/gl_highlighting.int and b/artifacts/mods/gl_highlighting.int differ diff --git a/artifacts/mods/gl_npcarmor.int b/artifacts/mods/gl_npcarmor.int index fe34af49..1fb06619 100644 Binary files a/artifacts/mods/gl_npcarmor.int and b/artifacts/mods/gl_npcarmor.int differ diff --git a/artifacts/mods/gl_npcarmor.ssl b/artifacts/mods/gl_npcarmor.ssl index 25d062d8..422154cd 100644 --- a/artifacts/mods/gl_npcarmor.ssl +++ b/artifacts/mods/gl_npcarmor.ssl @@ -15,9 +15,13 @@ #include "main.h" -#define IS_ARMOR(item) (obj_type(item) == OBJ_TYPE_ITEM and obj_item_subtype(item) == item_type_armor) +#define IS_ARMOR(item) (obj_type(item) == OBJ_TYPE_ITEM and obj_item_subtype(item) == item_type_armor) #define IS_WEAPON(item) (obj_type(item) == OBJ_TYPE_ITEM and obj_item_subtype(item) == item_type_weapon) +#define FID_OBJ_CRITTER (0x01000000) + +procedure update_armor_apperance; + variable modIni := "npcarmor.ini", defaultFids, @@ -26,44 +30,55 @@ variable altWeapon, unWieldWeapon; -procedure check_armor_change(variable critter, variable item, variable isWorn) begin +/* + Returns the FID value set in the config settings for party member armor: Jacket, Leather, etc. +*/ +procedure check_armor_change(variable critter, variable armor, variable isWield) begin variable npc, armorType, fid; - if (npcMap[obj_pid(critter)]) then begin - npc := npcMap[obj_pid(critter)]; - if (not isWorn) then begin + npc := npcMap[obj_pid(critter)]; + if (npc) then begin + if (isWield == false) then begin // display_msg("No armor fid: " + npc["Default"]); return npc["Default"]; - end else if (item) then begin - armorType := armorPidMap[obj_pid(item)]; + end else if (armor) then begin + armorType := armorPidMap[obj_pid(armor)]; fid := npc[armorType] or defaultFids[armorType]; if (fid == 0 or fid == -1) then fid := npc["Default"]; // display_msg("Change fid: " + fid + ", npc: " + npc[armorType] + ", default:" + defaultFids[armorType]); return fid; end end - return -1; + return -1; // engine default end -procedure check_weapon_change(variable critter, variable item, variable isWield) begin +/* + Checks if a party member can use the offered weapon +*/ +procedure check_weapon_change(variable critter, variable weapon, variable isWield) begin variable npc, newWeaponAnim, weaponAnimList, i; - if (npcMap[obj_pid(critter)]) then begin - npc := npcMap[obj_pid(critter)]; - if isWield then begin - newWeaponAnim := get_proto_data(obj_pid(item), PROTO_WP_ANIM); - weaponAnimList := string_split(npc["WeaponAnims"], ","); + npc := npcMap[obj_pid(critter)]; + if (npc) then begin + if (isWield) then begin + newWeaponAnim := get_proto_data(obj_pid(weapon), PROTO_WP_ANIM); if newWeaponAnim then begin // anim code 0 - none/unarmed - foreach (i in weaponAnimList) begin - if (newWeaponAnim == atoi(i)) then return -1; + weaponAnimList := npc["WeaponAnims"]; + if (weaponAnimList != 0) then begin + foreach (i in string_split(weaponAnimList, ",")) begin + if (newWeaponAnim == atoi(i)) then return -1; // can use (engine default) + end end - return 0; + return 0; // can't use end end else begin - unWieldWeapon := obj_pid(item); + unWieldWeapon := obj_pid(weapon); end end - return -1; + return -1; // engine default end +/* + Finds the first weapon which can be used by the party member in the inventory +*/ procedure search_alt_weapon(variable critter) begin variable obj, res, i := 0; obj := inven_ptr(critter, 0); @@ -87,48 +102,47 @@ end // for NPCs when they change armor/weapon themselves procedure invenwield_handler begin - variable critter, item, fid, slot, isWorn, canWield; + variable critter, item, fid, slot, isWield, canWield; critter := get_sfall_arg; - item := get_sfall_arg; - slot := get_sfall_arg; - isWorn := get_sfall_arg; + item := get_sfall_arg; + slot := get_sfall_arg; + isWield := get_sfall_arg; - if (critter and item and slot == INVEN_TYPE_WORN) then begin - fid := check_armor_change(critter, item, isWorn); + if (/*critter and*/ item and slot == INVEN_TYPE_WORN) then begin + fid := check_armor_change(critter, item, isWield); if (fid != -1) then begin if art_exists(fid) then begin art_change_fid_num(critter, fid); end else begin - debug_msg("Error: NPC Armor mod: missing FID."); + debug_msg("[Error] NPC armor mod: Invenwield missing FID: " + fid); end end - return; - end - - if (critter and item and slot == INVEN_TYPE_RIGHT_HAND) then begin + end else if (/*critter and*/ item and slot == INVEN_TYPE_RIGHT_HAND) then begin if (altWeapon == item) then return; - canWield := check_weapon_change(critter, item, isWorn); + canWield := check_weapon_change(critter, item, isWield); set_sfall_return(canWield); if (canWield != -1) then begin - call search_alt_weapon(critter); + call search_alt_weapon(critter); // wield weapon by script end end end // when changing armor from inventory while controlling other NPCs procedure adjustfid_handler begin - variable fid, armor; + variable fid, armor, weapAnim, currFid, newFid; if (dude_obj != real_dude_obj) then begin + currFid := get_sfall_arg; + if ((currFid bwand 0x0F000000) != FID_OBJ_CRITTER) then return; armor := critter_inven_obj(dude_obj, INVEN_TYPE_WORN); fid := check_armor_change(dude_obj, armor, armor != 0); if (fid != -1) then begin - variable weapAnim := get_sfall_arg bwand 0xF000; - variable newFid := fid bwand 0xFFFF0FFF bwor weapAnim; + weapAnim := currFid bwand 0xF000; + newFid := fid bwand 0xFFFF0FFF bwor weapAnim; if art_exists(newFid) then begin set_sfall_arg(0, newFid); set_sfall_return(newFid); end else begin - debug_msg("Error: NPC Armor mod: missing FID."); + debug_msg("[Error] NPC armor mod: Adjust FID missing: " + newFid); end end end @@ -140,29 +154,18 @@ procedure inventorymove_handler begin slot := get_sfall_arg; item := get_sfall_arg; if (dude_obj != real_dude_obj) then begin - if (IS_WEAPON(item) and (slot == INVEN_TYPE_RIGHT_HAND or slot == INVEN_TYPE_LEFT_HAND)) then begin + if ((slot == INVEN_TYPE_RIGHT_HAND or slot == INVEN_TYPE_LEFT_HAND) and IS_WEAPON(item)) then begin canWield := check_weapon_change(dude_obj, item, item != 0); set_sfall_return(canWield); end end end - procedure start begin variable sect, sects, armorTypes, armorType, npc, pid, pids, i; if game_loaded and (sfall_ver_major >= 4) then begin - register_hook_proc(HOOK_INVENWIELD, invenwield_handler); - register_hook_proc(HOOK_ADJUSTFID, adjustfid_handler); - register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler); - - defaultFids := get_ini_section(modIni, "Default"); - fix_array(defaultFids); - foreach (armorType: i in defaultFids) begin - defaultFids[armorType] := atoi(i); - end - - armorPidMap := create_array_map; armorTypes := get_ini_section(modIni, "ArmorTypes"); + armorPidMap := create_array_map; foreach (armorType: pids in armorTypes) begin pids := string_split(pids, ","); foreach (pid in pids) begin @@ -170,6 +173,12 @@ procedure start begin end end + defaultFids := get_ini_section(modIni, "Default"); + fix_array(defaultFids); + foreach (armorType: i in defaultFids) begin + defaultFids[armorType] := atoi(i); + end + npcMap := create_array_map; i := 1; @@ -188,7 +197,26 @@ procedure start begin i += 1; sect := get_ini_section(modIni, ""+i); end - end + call update_armor_apperance; + + register_hook_proc(HOOK_INVENWIELD, invenwield_handler); + register_hook_proc(HOOK_ADJUSTFID, adjustfid_handler); + register_hook_proc(HOOK_INVENTORYMOVE, inventorymove_handler); + + debug_msg("NPC armor appearance mod: Done."); + end end +procedure update_armor_apperance begin + variable npc, arItem, fid; + foreach (npc in party_member_list_critters) begin + if (npc == dude_obj) then continue; + arItem := critter_inven_obj(npc, INVEN_TYPE_WORN); + if (arItem) then begin + fid := check_armor_change(npc, arItem, true); + if (fid == -1 or art_exists(fid) == false) then continue; + art_change_fid_num(npc, fid); + end + end +end diff --git a/artifacts/mods/gl_partycontrol.int b/artifacts/mods/gl_partycontrol.int index 3d56e1b9..297282a7 100644 Binary files a/artifacts/mods/gl_partycontrol.int and b/artifacts/mods/gl_partycontrol.int differ diff --git a/artifacts/mods/gl_partycontrol.ssl b/artifacts/mods/gl_partycontrol.ssl index 8bf38d85..d6f7aae3 100644 --- a/artifacts/mods/gl_partycontrol.ssl +++ b/artifacts/mods/gl_partycontrol.ssl @@ -38,7 +38,7 @@ procedure combatturn_handler begin critter := get_sfall_arg, pid, perkID, level; - //display_msg("Combat Turn: " + status + ", by " + obj_name(critter) + ", arg3: " + get_sfall_arg); + //display_msg("Combat Turn: " + status + ", by " + obj_name(critter) + "/" + critter + ", arg3: " + get_sfall_arg); if npcControl then begin if lightInt then lightInt := round((lightInt / 65536.0) * 100); // calc percent of intensity obj_set_light_level(npcControl, lightInt, lightDist); // restore light for prev. controlled NPC @@ -68,6 +68,8 @@ procedure combatturn_handler begin npcControl := 0; // dude control end if inControl then begin + // check preference setting _combat_highlight + if (read_byte(0x56D38C)) then set_outline(critter, OUTLINE_GREY); // center the screen on the controlled critter/dude and remove roof tiles move_to(dude_obj, dude_tile, dude_elevation); if (displayName and critter != real_dude_obj) then begin @@ -77,6 +79,13 @@ procedure combatturn_handler begin end end end else if inControl then begin + // remove perks before switching control (only work with 4.1.8+) + foreach (perkID in perksList) begin + level := has_trait(TRAIT_PERK, real_dude_obj, perkID); + if (level) then begin + critter_rm_trait(critter, TRAIT_PERK, perkID, level); + end + end if isShowTag then begin hide_iface_tag(displayName); isShowTag := false; diff --git a/artifacts/mods/main.h b/artifacts/mods/main.h index 8e09b02d..13140aaa 100644 --- a/artifacts/mods/main.h +++ b/artifacts/mods/main.h @@ -2,10 +2,12 @@ #include "..\scripting\headers\sfall.h" #include "..\scripting\headers\define_lite.h" #include "..\scripting\headers\define_extra.h" - +#include "..\scripting\headers\dik.h" +/* #include "..\scripting\headers\lib.arrays.h" #include "..\scripting\headers\lib.strings.h" -// #include "..\scripting\headers\lib.inven.h" +#include "..\scripting\headers\lib.inven.h" +*/ variable ini := "sfall-mods.ini"; variable translationIni; diff --git a/artifacts/scripting/arrays.txt b/artifacts/scripting/arrays.txt index 14542a0e..faa896f9 100644 --- a/artifacts/scripting/arrays.txt +++ b/artifacts/scripting/arrays.txt @@ -212,9 +212,11 @@ use macros sort_array, sort_array_reverse, reverse_array, shuffle_array from sfa - always returns 0 > void save_array(mixed key, int arrayID): +- makes the array saveable; it will be saved in sfallgv.sav file when saving the game - arrayID is associated with given "key" - array becomes permanent (if it was temporary) and "saved" - key can be of any type (int, float or string) +- if you specify 0 as the key for the array ID, it will make the array "unsaved" > int load_array(mixed key): - load array from savegame data by the same key provided in "save_array" diff --git a/artifacts/scripting/headers/command_lite.h b/artifacts/scripting/headers/command_lite.h new file mode 100644 index 00000000..5d61dc34 --- /dev/null +++ b/artifacts/scripting/headers/command_lite.h @@ -0,0 +1,567 @@ +/* + Copyright 1998-2003 Interplay Entertainment Corp. All rights reserved. +*/ + +#ifndef COMMAND_H +#define COMMAND_H + + +#define critter_is_armed(x) (((obj_item_subtype(critter_inven_obj(x,INVEN_TYPE_RIGHT_HAND))) == item_type_weapon) or \ + ((obj_item_subtype(critter_inven_obj(x,INVEN_TYPE_LEFT_HAND))) == item_type_weapon)) +#define critter_weight(x) (100 + ((get_critter_stat(x,STAT_gender) == GENDER_MALE) * 50) + (get_critter_stat(x,STAT_st) * 5) - ((get_critter_stat(x,STAT_ag) + get_critter_stat(x,STAT_en))/3)) +#define critter_wearing_armor(x) (obj_item_subtype(critter_inven_obj(x,INVEN_TYPE_WORN)) == item_type_armor) + +#define dude_is_stupid (dude_iq <= 3) +#define dude_name (obj_name(dude_obj)) +#define dude_gender (get_critter_stat(dude_obj,STAT_gender)) +#define dude_strength (get_critter_stat(dude_obj,STAT_st)) +#define dude_perception (get_critter_stat(dude_obj,STAT_pe)) +#define dude_endurance (get_critter_stat(dude_obj,STAT_en)) +#define dude_charisma (get_critter_stat(dude_obj,STAT_ch)) +#define dude_iq (get_critter_stat(dude_obj,STAT_iq)) +#define dude_agility (get_critter_stat(dude_obj,STAT_ag)) +#define dude_luck (get_critter_stat(dude_obj,STAT_lu)) +#ifndef dude_level +#define dude_level (get_pc_stat(PCSTAT_level)) +#endif +#define dude_age (get_critter_stat(dude_obj,STAT_age)) +#define dude_weight critter_weight(dude_obj) + +#define dude_moron_not_vegetable (((get_critter_stat(dude_obj,STAT_iq)) > 1) and ((get_critter_stat(dude_obj,STAT_iq)) < 3) ) + +#define dude_is_male (dude_gender == GENDER_MALE) +#define dude_is_female (dude_gender == GENDER_FEMALE) +#define dude_is_armed critter_is_armed(dude_obj) +#define dude_wearing_armor critter_wearing_armor(dude_obj) + +#define dude_has_power_armor (((obj_is_carrying_obj_pid(dude_obj, PID_POWERED_ARMOR)) + \ + (obj_is_carrying_obj_pid(dude_obj, PID_ADVANCED_POWER_ARMOR)) + \ + (obj_is_carrying_obj_pid(dude_obj, PID_ADVANCED_POWER_ARMOR_MK2)) + \ + (obj_is_carrying_obj_pid(dude_obj, PID_HARDENED_POWER_ARMOR))) > 0) + +#define dude_wielding_knife ((obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_RIGHT_HAND)) == PID_KNIFE) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_RIGHT_HAND)) == PID_THROWING_KNIFE) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_RIGHT_HAND)) == PID_RIPPER) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_RIGHT_HAND)) == PID_COMBAT_KNIFE) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_LEFT_HAND)) == PID_KNIFE) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_LEFT_HAND)) == PID_THROWING_KNIFE) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_LEFT_HAND)) == PID_RIPPER) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_LEFT_HAND)) == PID_COMBAT_KNIFE)) + +#define dude_wearing_power_armor ((obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_POWERED_ARMOR) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_ADVANCED_POWER_ARMOR) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_ADVANCED_POWER_ARMOR_MK2) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_HARDENED_POWER_ARMOR)) + +#define dude_wearing_leather_armor ((obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_LEATHER_ARMOR) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_LEATHER_JACKET) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_LEATHER_ARMOR_MK_II) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_CURED_LEATHER_ARMOR)) + +#define dude_wearing_combat_armor ((obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_COMBAT_ARMOR) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_BROTHERHOOD_COMBAT_ARMOR) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_COMBAT_ARMOR_MK_II)) + +#define dude_wearing_metal_armor ((obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_METAL_ARMOR) or \ + (obj_pid(critter_inven_obj(dude_obj,INVEN_TYPE_WORN)) == PID_METAL_ARMOR_MK_II)) + +#define dude_wearing_vault_suit COND019(1) +#define dude_has_gambling_skills ((dude_iq > 3) and (has_skill(dude_obj, SKILL_GAMBLING) >= 25)) + +#define dude_is_sneaking (using_skill(dude_obj,SKILL_SNEAK)) +#define dude_is_walking (art_anim(obj_art_fid(dude_obj)) == ANIM_walk) +#define dude_is_running (art_anim(obj_art_fid(dude_obj)) == ANIM_running) +#define dude_is_poisoned (get_poison(dude_obj)) +#define dude_is_irradiated (get_critter_stat(dude_obj, STAT_current_rad)) +#define dude_is_crippled ((critter_state(dude_obj) bwand DAM_CRIP_LEG_LEFT) or \ + (critter_state(dude_obj) bwand DAM_CRIP_LEG_RIGHT) or \ + (critter_state(dude_obj) bwand DAM_CRIP_ARM_LEFT) or \ + (critter_state(dude_obj) bwand DAM_CRIP_ARM_RIGHT)) + +#define dude_cur_rot (has_trait(TRAIT_OBJECT,dude_obj,OBJECT_CUR_ROT)) +#define dude_inv_rot ((dude_cur_rot + 3)%6) +#define dude_tile (tile_num(dude_obj)) +#define dude_elevation (elevation(dude_obj)) + +#define tile_behind_obj(who) (tile_num_in_direction(tile_num(who),((has_trait(TRAIT_OBJECT,who,OBJECT_CUR_ROT) + 3) % 6), 1)) + +#define dude_cur_hits (get_critter_stat(dude_obj,STAT_current_hp)) +#define dude_max_hits (get_critter_stat(dude_obj,STAT_max_hp)) + +#define dude_mental_block (has_trait(TRAIT_PERK,dude_obj,PERK_mental_block)) +#define dude_presence (has_trait(TRAIT_PERK,dude_obj,PERK_presence)) +#define dude_awareness (has_trait(TRAIT_PERK,dude_obj,PERK_bonus_awareness)) +#define dude_fortune_finder (has_trait(TRAIT_PERK,dude_obj,PERK_fortune_finder)) +#define dude_smooth_talker (has_trait(TRAIT_PERK,dude_obj,PERK_smooth_talker)) + +#define dude_caps (item_caps_total(dude_obj)) + +#define dude_has_car (global_var(GVAR_PLAYER_GOT_CAR)) +#define dude_is_pornstar dude_has_porn_star_rep +#define dude_is_high drug_influence(dude_obj) +#define dude_found_geck global_var(GVAR_VAULT13_FOUND_GECK) + + +#define dude_stat_win(X,Y) ((do_check(dude_obj,X,Y)) > (do_check(self_obj,X,0))) +#define dude_stat_lose(X,Y) ((do_check(dude_obj,X,Y)) < (do_check(self_obj,X,0))) +#define dude_stat_tie(X,Y) ((do_check(dude_obj,X,Y)) == (do_check(self_obj,X,0))) +#define dude_can_see_self obj_can_see_obj(dude_obj,self_obj) + +#define dude_item(x) obj_carrying_pid_obj(dude_obj, x) +#define dude_item_count(x) obj_is_carrying_obj_pid(dude_obj, x) + +#define dude_is_addict (has_trait(TRAIT_TRAIT, dude_obj, TRAIT_drug_addict)) + + +#define self_name (obj_name(self_obj)) +#define self_gender (get_critter_stat(self_obj,STAT_gender)) +#define self_strength (get_critter_stat(self_obj,STAT_st)) +#define self_perception (get_critter_stat(self_obj,STAT_pe)) +#define self_endurance (get_critter_stat(self_obj,STAT_en)) +#define self_charisma (get_critter_stat(self_obj,STAT_ch)) +#define self_iq (get_critter_stat(self_obj,STAT_iq)) +#define self_agility (get_critter_stat(self_obj,STAT_ag)) +#define self_luck (get_critter_stat(self_obj,STAT_lu)) + +#define self_is_male (self_gender == GENDER_MALE) +#define self_is_female (self_gender == GENDER_FEMALE) +#define self_is_armed critter_is_armed(self_obj) +#define self_wearing_armor critter_wearing_armor(self_obj) + +#define self_carrying_laser_pistol ((obj_pid(critter_inven_obj(self_obj,INVEN_TYPE_LEFT_HAND)) == PID_LASER_PISTOL) or \ + (obj_pid(critter_inven_obj(self_obj,INVEN_TYPE_RIGHT_HAND)) == PID_LASER_PISTOL)) + +#define self_is_sneaking (using_skill(self_obj,SKILL_SNEAK)) +#define self_is_walking (art_anim(obj_art_fid(self_obj)) == ANIM_walk) +#define self_is_running (art_anim(obj_art_fid(self_obj)) == ANIM_running) +#define self_is_poisoned (get_poison(self_obj)) + +#define self_cur_rot (has_trait(TRAIT_OBJECT,self_obj,OBJECT_CUR_ROT)) +#define self_inv_rot ((self_cur_rot + 3)%6) +#define self_tile (tile_num(self_obj)) +#define self_elevation (elevation(self_obj)) + +#define self_pid (obj_pid(self_obj)) +#define self_team has_trait(TRAIT_OBJECT,self_obj,OBJECT_TEAM_NUM) +#define self_ai has_trait(TRAIT_OBJECT,self_obj,OBJECT_AI_PACKET) +#define self_visible obj_is_visible_flag(self_obj) + +#define self_cur_hits (get_critter_stat(self_obj,STAT_current_hp)) +#define self_max_hits (get_critter_stat(self_obj,STAT_max_hp)) + +#define self_mental_block (has_trait(TRAIT_PERK,self_obj,PERK_mental_block)) +#define self_presence (has_trait(TRAIT_PERK,self_obj,PERK_presence)) +#define self_awareness (has_trait(TRAIT_PERK,self_obj,PERK_bonus_awareness)) +#define self_fortune_finder (has_trait(TRAIT_PERK,self_obj,PERK_fortune_finder)) + +#define self_caps (item_caps_total(self_obj)) +#define self_fid (obj_art_fid(self_obj)) + +#define skill_success(x,y,z) (is_success(roll_vs_skill(x,y,z))) +#define stat_success(x,y,z) (is_success(do_check(x,y,z))) + +#define self_can_see_dude obj_can_see_obj(self_obj,dude_obj) +#define self_distance_from_dude tile_distance(self_tile, dude_tile) +#define self_is_high drug_influence(self_obj) + +#define self_item(x) obj_carrying_pid_obj(self_obj, x) +#define self_item_count(x) obj_is_carrying_obj_pid(self_obj, x) + + +// some timer event macros +#define check_set_obj_visiblility(the_obj, x) if (obj_is_visible_flag(the_obj) == x) then set_obj_visibility(the_obj, x) + +#define flush_add_timer_event(obj, time, param) rm_fixed_timer_event(obj, param); \ + add_timer_event(obj, time, param) +#define flush_add_timer_event_sec(obj, sec, param) flush_add_timer_event(obj, game_ticks(sec), param) + +/****************************************************************** + General commands getting information about time +******************************************************************/ + +#define morning ((game_time_hour >= 600) and (game_time_hour < 700)) +#ifndef day +#define day ((game_time_hour >= 700) and (game_time_hour < 1800)) +#endif +#define evening ((game_time_hour >= 1800) and (game_time_hour < 1900)) +#ifndef night +#define night ((game_time_hour >= 1900) or (game_time_hour < 600)) +#endif + +#define GAME_TIME_SUBSECOND_RESOLUTION (10) +#define SECONDS_PER_MINUTE (60) +#define MINUTES_PER_HOUR (60) +#define HOURS_PER_DAY (24) +#define DAYS_PER_YEAR (365) + // Seconds Minutes Hours Days Months ==> Year +#define GAME_TIME_IN_YEARS (game_time / (GAME_TIME_SUBSECOND_RESOLUTION * SECONDS_PER_MINUTE * MINUTES_PER_HOUR * HOURS_PER_DAY * DAYS_PER_YEAR)) +#define GAME_TIME_IN_DAYS (game_time / (GAME_TIME_SUBSECOND_RESOLUTION * 60 * 60 * 24)) +#define GAME_TIME_IN_HOURS (game_time / (GAME_TIME_SUBSECOND_RESOLUTION * SECONDS_PER_MINUTE * MINUTES_PER_HOUR)) +#define GAME_TIME_IN_MINUTES (game_time / (GAME_TIME_SUBSECOND_RESOLUTION * SECONDS_PER_MINUTE)) +#define GAME_TIME_IN_SECONDS (game_time / GAME_TIME_SUBSECOND_RESOLUTION) + + +#define ticks_til_hour(x) ((ONE_GAME_DAY * (game_time_hour > (x))) + ((((x)/100) - (game_time_hour/100)) * ONE_GAME_HOUR) + ((((x)%100) - (game_time_hour%100)) * ONE_GAME_MINUTE)) +#define game_time_advance_hour(x) game_time_advance(ticks_til_hour(x)) + +// use times in define.h for passed_time(example: ONE_GAME_DAY) +#define time_has_passed(old_time, passed_time) ((passed_time + old_time) <= game_time) +// get the actual amout of time that has passed +#define amt_time_has_passed(old_time, time_unit) ((game_time - old_time) / time_unit) + +#define one_second_passed(old_time) time_has_passed(old_time,ONE_GAME_SECOND) +#define one_minute_passed(old_time) time_has_passed(old_time,ONE_GAME_MINUTE) +#define one_hour_passed(old_time) time_has_passed(old_time,ONE_GAME_HOUR) +#define one_day_passed(old_time) time_has_passed(old_time,ONE_GAME_DAY) +#define one_week_passed(old_time) time_has_passed(old_time,ONE_GAME_WEEK) +#define one_month_passed(old_time) time_has_passed(old_time,ONE_GAME_MONTH) + +/*************************************************************** +**************** Dialogue Information *********************** +***************************************************************/ +#ifndef floater + #define floater(x) float_msg(self_obj, message_str(NAME,x), FLOAT_COLOR_NORMAL) +#endif +#define floater_rand(x,y) floater(random(x,y)) + +#define dude_floater(x) float_msg(dude_obj, message_str(NAME,x), FLOAT_COLOR_NORMAL) +#define dude_floater_rand(x,y) dude_floater(random(x,y)) +#define dude_floater_bad(x) float_msg(dude_obj, message_str(NAME,x), FLOAT_COLOR_BAD) +#define dude_floater_sick(x) float_msg(dude_obj, message_str(NAME,x), FLOAT_COLOR_SICK) + + +#define FLOAT_COLOR_NORMAL FLOAT_MSG_YELLOW +#define FLOAT_COLOR_SICK FLOAT_MSG_GREEN +#define FLOAT_COLOR_BAD FLOAT_MSG_RED +#define FLOAT_COLOR_GOOD FLOAT_MSG_WHITE +#define FLOAT_COLOR_SFX FLOAT_MSG_LIGHT_GREY +#define FLOAT_COLOR_SAD FLOAT_MSG_GREY +#define FLOAT_COLOR_AFRAID FLOAT_MSG_LIGHT_RED +#define FLOAT_COLOR_PAIN FLOAT_MSG_LIGHT_GREY +#define FLOAT_COLOR_HIGH FLOAT_MSG_PURPLE +#define FLOAT_COLOR_WHISPER FLOAT_MSG_GREY + +#define obj_floater_type(z,x,type) float_msg(z, x, FLOAT_COLOR_NORMAL + ((type - FLOAT_COLOR_NORMAL) * has_trait(TRAIT_PERK, dude_obj, PERK_empathy))) +#define floater_type_msg(x, type) obj_floater_type(self_obj, x, type) +#define floater_type(x, type) floater_type_msg(mstr(x), type) +#define floater_bad(x) floater_type(x, FLOAT_COLOR_BAD) +#define floater_bad_rand(x,y) floater_bad(random(x,y)) +#define floater_sick(x) floater_type(x, FLOAT_COLOR_SICK) +#define floater_sick_rand(x,y) floater_sick(random(x,y)) +#define floater_afraid(x) floater_type(x, FLOAT_COLOR_AFRAID) +#define floater_afraid_rand(x,y) floater_afraid(random(x,y)) +#define floater_high(x) floater_type(x, FLOAT_COLOR_HIGH) +#define floater_high_rand(x,y) floater_high(random(x,y)) +#define floater_good(x) floater_type(x, FLOAT_COLOR_GOOD) +#define floater_good_rand(x,y) floater_good(random(x,y)) + +#define mstr(x) message_str(NAME,x) +#define display_mstr(x) display_msg(mstr(x)) +#define g_mstr(x) message_str(SCRIPT_GENERIC,x) +#define g_grunt g_mstr(random(300,303)) +#define g_bye g_mstr(random(375,376)) + +#define LOW_IQ (-3) + +#define Reply(x) gSay_Reply(NAME,x) +#define Reply_Rand(x,y) Reply(random(x,y)) +#define GMessage(x) gSay_Message(NAME,x,GOOD_REACTION) +#define NMessage(x) gSay_Message(NAME,x,NEUTRAL_REACTION) +#define BMessage(x) gSay_Message(NAME,x,BAD_REACTION) + +// this converts a number from 0-100 to the text representing the number, ie: 1 -> "one", 40 -> "forty-one" +#define num_to_num_text(x) g_mstr(2000+x) + +// Option(Message Number, Node Number, IQ) ==> giQ_Option(IQ, Message Number, Node Number) -rwh2 (11/12/97) +#define GOption(x,y,z) giQ_Option(z,NAME,x,y,GOOD_REACTION) +#define NOption(x,y,z) giQ_Option(z,NAME,x,y,NEUTRAL_REACTION) +#define BOption(x,y,z) giQ_Option(z,NAME,x,y,BAD_REACTION) +#define GLowOption(x,y) giQ_Option(LOW_IQ,NAME,x,y,GOOD_REACTION) +#define NLowOption(x,y) giQ_Option(LOW_IQ,NAME,x,y,NEUTRAL_REACTION) +#define BLowOption(x,y) giQ_Option(LOW_IQ,NAME,x,y,BAD_REACTION) + +#define display_mstr_rand(x,y) display_msg(mstr(random(x,y))) + +/*************************************************************** +**************** Global Variable commands ********************* +***************************************************************/ +#define inc_global_var_amt(x,y) set_global_var(x,global_var(x)+y) +#define inc_global_var(x) inc_global_var_amt(x,1) +#define dec_global_var_amt(x,y) set_global_var(x,global_var(x)-y) +#define dec_global_var(x) dec_global_var_amt(x,1) + +/*************************************************************** +**************** map Variable commands ************************ +***************************************************************/ +#define inc_map_var_amt(x,y) set_map_var(x,map_var(x)+y) +#define inc_map_var(x) inc_map_var_amt(x,1) +#define dec_map_var_amt(x,y) set_map_var(x,map_var(x)-y) +#define dec_map_var(x) dec_map_var_amt(x,1) + +/*************************************************************** +**************** Local Variable commands ********************** +***************************************************************/ +#define inc_local_var_amt(x,y) set_local_var(x,local_var(x)+y) +#define inc_local_var(x) inc_local_var_amt(x,1) +#define dec_local_var_amt(x,y) set_local_var(x,local_var(x)-y) +#define dec_local_var(x) dec_local_var_amt(x,1) + +/*************************************************************** +**************** Bit Manipulation Macros *********************** +***************************************************************/ +#define MAXINT 4294967295 //Max int +#define INV(x) (MAXINT - x) //An inverse function + +// get bit state +#define gvar_bit(x, the_bit) ((global_var(x) bwand the_bit) != 0) +#define mvar_bit(x, the_bit) ((map_var(x) bwand the_bit) != 0) +#define lvar_bit(x, the_bit) ((local_var(x) bwand the_bit) != 0) + +// switch bit on +#define set_gvar_bit_on(x, the_bit) set_global_var(x, global_var(x) bwor the_bit) +#define set_mvar_bit_on(x, the_bit) set_map_var(x, map_var(x) bwor the_bit) +#define set_lvar_bit_on(x, the_bit) set_local_var(x, local_var(x) bwor the_bit) + +// switch bit off +#define set_gvar_bit_off(x, the_bit) set_global_var(x, global_var(x) bwand INV(the_bit)) +#define set_mvar_bit_off(x, the_bit) set_map_var(x, map_var(x) bwand INV(the_bit)) +#define set_lvar_bit_off(x, the_bit) set_local_var(x, local_var(x) bwand INV(the_bit)) + +// truncates a number +#define trunc(x) (x * -(x<1)) + +/****************************************************************** + General commands getting information about a critter +******************************************************************/ +#define critter_is_male (get_critter_stat(self_obj,STAT_gender) == GENDER_MALE) +#define critter_is_female (get_critter_stat(self_obj,STAT_gender) == GENDER_FEMALE) +/*#define critter_is_armed (((obj_item_subtype(critter_inven_obj(self_obj,INVEN_TYPE_RIGHT_HAND))) == item_type_weapon) \ + or ((obj_item_subtype(critter_inven_obj(self_obj,INVEN_TYPE_LEFT_HAND))) == item_type_weapon)) +*/ +/****************************************************************** + General commands setting and getting the map exit +******************************************************************/ +// note set either exit or next to 0 for invalid +#define set_map_exit_tiles(the_exit,the_next) set_global_var(GVAR_MAP_EXIT_TILE,the_exit); set_global_var(GVAR_MAP_NEXT_TILE,the_next) +#define map_exit_tile (global_var(GVAR_MAP_EXIT_TILE)) +#define map_next_tile (global_var(GVAR_MAP_NEXT_TILE)) + +/****************************************************************** + General commands for checking and changing status variables + note: variable needs to be global for the functions to work + note: the defines for the status's are in GLOBAL.H +******************************************************************/ +#define change_status_on(status_var, which_status) set_gvar_bit_on(status_var, which_status); +#define change_status_off(status_var, which_status) set_gvar_bit_off(status_var, which_status); + +#define set_status_known(status_var) change_status_on(status_var, PERSON_KNOWN) +#define set_status_attacked(status_var) change_status_on(status_var, PERSON_ATTACKED) +#define set_status_killed(status_var) change_status_on(status_var, PERSON_KILLED) +#define set_status_assassinated(status_var) change_status_on(status_var, PERSON_ASSASSINATED) +#define set_status_dead(status_var) change_status_on(status_var, PERSON_DEAD) +#define set_status_spoken_to(status_var) change_status_on(status_var, PERSON_SPOKEN_TO) +#define set_status_spoken_to_special(status_var) change_status_on(status_var, PERSON_SPOKEN_TO_SPECIAL) +#define set_status_special(status_var) change_status_on(status_var, PERSON_SPECIAL) +#define set_status_special_2(status_var) change_status_on(status_var, PERSON_SPECIAL_2) +#define set_status_in_party(status_var) change_status_on(status_var, PERSON_IN_PARTY) +#define set_status_not_in_party(status_var) change_status_off(status_var, PERSON_IN_PARTY) +#define set_status_warned(status_var) change_status_on(status_var, PERSON_WARNED) +#define set_status_sex(status_var) change_status_on(status_var, PERSON_SEX) + +#define status_is(status_var, check_status) (gvar_bit(status_var, check_status)) + +#define status_is_known(status_var) (status_is(status_var, PERSON_KNOWN)) +#define status_is_unknown(status_var) (status_is_known(status_var) == false) +#define status_is_attacked(status_var) (status_is(status_var, PERSON_ATTACKED)) +#define status_is_killed(status_var) (status_is(status_var, PERSON_KILLED)) +#define status_is_assassinated(status_var) (status_is(status_var, PERSON_ASSASSINATED)) +#define status_is_dead(status_var) (status_is(status_var, PERSON_DEAD)) +#define status_is_spoken_to(status_var) (status_is(status_var, PERSON_SPOKEN_TO)) +#define status_is_spoken_to_special(status_var) (status_is(status_var, PERSON_SPOKEN_TO_SPECIAL)) +#define status_is_special(status_var) (status_is(status_var, PERSON_SPECIAL)) +#define status_is_special_2(status_var) (status_is(status_var, PERSON_SPECIAL_2)) +#define status_is_in_party(status_var) (status_is(status_var, PERSON_IN_PARTY)) +#define status_is_not_in_party(status_var) (status_is(status_var, PERSON_IN_PARTY) == false) +#define status_is_warned(status_var) (status_is(status_var, PERSON_WARNED)) +#define status_is_sex(status_var) (status_is(status_var, PERSON_SEX)) + +#define status_is_alive(status_var) (not (status_is_killed(status_var) or status_is_assassinated(status_var) or status_is_dead(status_var))) + + +/****************************************************************** + General commands getting information about cities +******************************************************************/ +#define abbey_visible_on_map (town_known(AREA_ABBEY) != MARK_STATE_UNKNOWN) +#define arroyo_visible_on_map (town_known(AREA_ARROYO) != MARK_STATE_UNKNOWN) +#define broken_hills_visible_on_map (town_known(AREA_BROKEN_HILLS) != MARK_STATE_UNKNOWN) +#define colusa_visible_on_map (town_known(AREA_NAVARRO) != MARK_STATE_UNKNOWN) +#define den_visible_on_map (town_known(AREA_DEN) != MARK_STATE_UNKNOWN) +#define enclave_visible_on_map (town_known(AREA_THE_ENCLAVE) != MARK_STATE_UNKNOWN) +#define EPA_visible_on_map (town_known(AREA_EPA) != MARK_STATE_UNKNOWN) +#define gecko_visible_on_map (town_known(AREA_GECKO) != MARK_STATE_UNKNOWN) +#define klamath_visible_on_map (town_known(AREA_KLAMATH) != MARK_STATE_UNKNOWN) +#define military_base_visible_on_map (town_known(AREA_MILITARY_BASE) != MARK_STATE_UNKNOWN) +#define modoc_visible_on_map (town_known(AREA_MODOC) != MARK_STATE_UNKNOWN) +#define new_CA_republic_visible_on_map (town_known(AREA_NCR) != MARK_STATE_UNKNOWN) +#define primitive_tribe_visible_on_map (town_known(AREA_PRIMITIVE_TRIBE) != MARK_STATE_UNKNOWN) +#define redding_visible_on_map (town_known(AREA_REDDING) != MARK_STATE_UNKNOWN) +#define new_reno_visible_on_map (town_known(AREA_NEW_RENO) != MARK_STATE_UNKNOWN) +#define san_francisco_visible_on_map (town_known(AREA_SAN_FRANCISCO) != MARK_STATE_UNKNOWN) +#define sierra_army_base_visible_on_map (town_known(AREA_SIERRA_ARMY_BASE) != MARK_STATE_UNKNOWN) +#define vault_13_visible_on_map (town_known(AREA_VAULT_13) != MARK_STATE_UNKNOWN) +#define vault_15_visible_on_map (town_known(AREA_VAULT_15) != MARK_STATE_UNKNOWN) +#define vault_city_visible_on_map (town_known(AREA_VAULT_CITY) != MARK_STATE_UNKNOWN) +#define vault_fake_13A_visible_on_map (town_known(AREA_FAKE_VAULT_13A) != MARK_STATE_UNKNOWN) +#define vault_fake_13B_visible_on_map (town_known(AREA_FAKE_VAULT_13B) != MARK_STATE_UNKNOWN) + +#define abbey_visit (town_known(AREA_ABBEY) == MARK_STATE_VISITED) +#define arroyo_visit (town_known(AREA_ARROYO) == MARK_STATE_VISITED) +#define broken_hills_visit (town_known(AREA_BROKEN_HILLS) == MARK_STATE_VISITED) +#define colusa_visit (town_known(AREA_NAVARRO) == MARK_STATE_VISITED) +#define den_visit (town_known(AREA_DEN) == MARK_STATE_VISITED) +#define enclave_visit (town_known(AREA_THE_ENCLAVE) == MARK_STATE_VISITED) +#define EPA_visit (town_known(AREA_EPA) == MARK_STATE_VISITED) +#define gecko_visit (town_known(AREA_GECKO) == MARK_STATE_VISITED) +#define klamath_visit (town_known(AREA_KLAMATH) == MARK_STATE_VISITED) +#define military_base_visit (town_known(AREA_MILITARY_BASE) == MARK_STATE_VISITED) +#define modoc_visit (town_known(AREA_MODOC) == MARK_STATE_VISITED) +#define new_CA_republic_visit (town_known(AREA_NCR) == MARK_STATE_VISITED) +#define primitive_tribe_visit (town_known(AREA_PRIMITIVE_TRIBE) == MARK_STATE_VISITED) +#define redding_visit (town_known(AREA_REDDING) == MARK_STATE_VISITED) +#define new_reno_visit (town_known(AREA_NEW_RENO) == MARK_STATE_VISITED) +#define san_francisco_visit (town_known(AREA_SAN_FRANCISCO) == MARK_STATE_VISITED) +#define sierra_army_base_visit (town_known(AREA_SIERRA_ARMY_BASE) == MARK_STATE_VISITED) +#define vault_13_visit (town_known(AREA_VAULT_13) == MARK_STATE_VISITED) +#define vault_15_visit (town_known(AREA_VAULT_15) == MARK_STATE_VISITED) +#define vault_city_visit (town_known(AREA_VAULT_CITY) == MARK_STATE_VISITED) +#define arroyo_destroyed_visit (town_known(AREA_ARROYO_DESTROYED) == MARK_STATE_VISITED) +#define vault_fake_13A_visit (town_known(AREA_FAKE_VAULT_13A) == MARK_STATE_VISITED) +#define vault_fake_13B_visit (town_known(AREA_FAKE_VAULT_13B) == MARK_STATE_VISITED) + +#define mark_on_map(x) if (town_known(x) == MARK_STATE_UNKNOWN) then begin \ + debug_msg(" mark_on_map("+x+")"); \ + mark_area_known(MARK_TYPE_TOWN, x, MARK_STATE_KNOWN); \ + end + +#define unmark_on_map(x) if (town_known(x) != MARK_STATE_UNKNOWN) then begin \ + debug_msg(" unmark_on_map("+x+")"); \ + mark_area_known(MARK_TYPE_TOWN, x, MARK_STATE_UNKNOWN); \ + end + +#define mark_abbey_on_map mark_on_map(AREA_ABBEY) +#define mark_arroyo_on_map mark_on_map(AREA_ARROYO) +#define mark_broken_hills_on_map mark_on_map(AREA_BROKEN_HILLS) +#define mark_colusa_on_map mark_on_map(AREA_NAVARRO) +#define mark_navarro_on_map mark_on_map(AREA_NAVARRO) +#define mark_den_on_map mark_on_map(AREA_DEN) +#define mark_enclave_on_map mark_on_map(AREA_THE_ENCLAVE) +#define mark_EPA_on_map mark_on_map(AREA_EPA) +#define mark_gecko_on_map mark_on_map(AREA_GECKO) +#define mark_klamath_on_map mark_on_map(AREA_KLAMATH) +#define mark_military_base_on_map mark_on_map(AREA_MILITARY_BASE) +#define mark_modoc_on_map mark_on_map(AREA_MODOC) +#define mark_new_CA_republic_on_map mark_on_map(AREA_NCR) +#define mark_primitive_tribe_on_map mark_on_map(AREA_PRIMITIVE_TRIBE) +#define mark_redding_on_map mark_on_map(AREA_REDDING) +#define mark_new_reno_on_map mark_on_map(AREA_NEW_RENO) +#define mark_san_francisco_on_map mark_on_map(AREA_SAN_FRANCISCO) +#define mark_sierra_army_base_on_map mark_on_map(AREA_SIERRA_ARMY_BASE) +#define mark_vault_13_on_map mark_on_map(AREA_VAULT_13) +#define mark_vault_15_on_map mark_on_map(AREA_VAULT_15) +#define mark_vault_city_on_map mark_on_map(AREA_VAULT_CITY) +#define mark_vault_fake_13A_on_map mark_on_map(AREA_FAKE_VAULT_13A) +#define mark_vault_fake_13B_on_map mark_on_map(AREA_FAKE_VAULT_13B) + +#define unmark_vault_fake_13A_on_map unmark_on_map(AREA_FAKE_VAULT_13A) + + + +/******************************************************************************************************************* +********** These represent the players acomplishments through the game ********************************************* +********** Note: these reflect the Fallout2 variable designers put in the scripts assuming we were ***************** +********** going to have a magic variable to keep track of the player stats throught the game ********************** +*******************************************************************************************************************/ +#define f2_state_v13_looking (0) +#define f2_state_v13_known (1) // set -Chris H +#define f2_state_v13_found (2) // set -Chris H +#define f2_state_v13_entered (3) // set -Chris H +#define f2_state_tribe_kidnapped (4) // ### state not set +#define f2_state_fix_tanker (5) // didn't need to exist +#define f2_state_tanker_fix_known (6) // set -Chris H +#define f2_state_tanker_fixed (7) // set -Chris H +#define f2_state_enclave_destroyed (8) // ### state not set + +#define set_f2(x) if (global_var(GVAR_FALLOUT_2) < x) then set_global_var(GVAR_FALLOUT_2, x) +#define force_f2(x) set_global_var(GVAR_FALLOUT_2, x) +#define get_f2(x) (global_var(GVAR_FALLOUT_2) == x) +#define f2_flag_any (global_var(GVAR_FALLOUT_2)) + +#define Fallout2_vault13_looking get_f2(f2_state_v13_looking) +#define Fallout2_vault13_location_known get_f2(f2_state_v13_known) +#define Fallout2_vault13_found get_f2(f2_state_v13_found) +#define Fallout2_vault13_entered get_f2(f2_state_v13_entered) +#define Fallout2_tribe_kidnapped get_f2(f2_state_tribe_kidnapped) +#define Fallout2_fix_tanker get_f2(f2_state_fix_tanker) +#define Fallout2_tanker_known get_f2(f2_state_tanker_fix_known) +#define Fallout2_tanker_fixed get_f2(f2_state_tanker_fixed) +#define Fallout2_enclave_destroyed get_f2(f2_state_enclave_destroyed) + +// Info relating to carry weight, how much something costs, and weights +#define pid_weight(x) proto_data(x,it_weight) +#define obj_weight(x) pid_weight(obj_pid(x)) +#define pid_cost(x) proto_data(x, it_cost) +#define obj_cost(x) pid_cost(obj_pid(x)) + +#define amt_cost_barter(who1, who2, x) floor(2 * x * ((160.0 + has_skill(who2,SKILL_BARTER)) / (160.0 + has_skill(who1,SKILL_BARTER)))) +#define pid_cost_barter(who1, who2, x) amt_cost_barter(who1, who2, pid_cost(x)) +#define obj_cost_barter(who1, who2, x) pid_cost_barter(who1, who2, obj_pid(x)) +#define who_max_carry(x) get_critter_stat(x,STAT_carry_amt) +#define who_carry_amount(x) has_trait(TRAIT_OBJECT, x, OBJECT_CUR_WEIGHT) +#define who_inven_free_amt(x) (who_max_carry(x) - who_carry_amount(x)) + + +/******************************************************************** +************ These are for cutting down on the lvars ************ +********************************************************************/ +// for examples of use, see new reno and raiders +// default bits for character flag +#define herebefore_bit bit_1 +#define hostile_bit bit_2 +#define examined_bit bit_3 +#define personal_enemy_bit bit_4 +#define initialized_bit bit_5 +#define is_float_bit bit_7 +#define destroy_me_bit bit_8 +#define heard_call_bit bit_9 + +// here before +#define herebefore lvar_bit(LVAR_Flags, herebefore_bit) +#define set_herebefore set_lvar_bit_on(LVAR_Flags, herebefore_bit) +#define off_herebefore set_lvar_bit_off(LVAR_Flags, herebefore_bit) +// hostile +#define hostile lvar_bit(LVAR_Flags, hostile_bit) +#define set_hostile set_lvar_bit_on(LVAR_Flags, hostile_bit) +#define off_hostile set_lvar_bit_off(LVAR_Flags, hostile_bit) +// examined +#define examined lvar_bit(LVAR_Flags, examined_bit) +#define set_examined set_lvar_bit_on(LVAR_Flags, examined_bit) +// personal enemy +#define personal_enemy lvar_bit(LVAR_Flags, personal_enemy_bit) +#define set_personal_enemy set_lvar_bit_on(LVAR_Flags, personal_enemy_bit); set_hostile +// initialized +#define destroy_me lvar_bit(LVAR_Flags, destroy_me_bit) +#define set_destroy_me set_lvar_bit_on(LVAR_Flags, destroy_me_bit) +// initialized +#define initialized lvar_bit(LVAR_Flags, initialized_bit) +#define set_initialized set_lvar_bit_on(LVAR_Flags, initialized_bit) +// heard_call +#define heard_call lvar_bit(LVAR_Flags, heard_call_bit) +#define set_heard_call set_lvar_bit_on(LVAR_Flags, heard_call_bit) +// set message type +#define is_float lvar_bit(LVAR_Flags, is_float_bit) +#define is_click (is_float == false) +#define set_is_float set_lvar_bit_on(LVAR_Flags, is_float_bit) +#define set_is_click set_lvar_bit_off(LVAR_Flags, is_float_bit) + + +#endif // COMMAND_H diff --git a/artifacts/scripting/headers/define_extra.h b/artifacts/scripting/headers/define_extra.h index 4884a008..2de1909c 100644 --- a/artifacts/scripting/headers/define_extra.h +++ b/artifacts/scripting/headers/define_extra.h @@ -120,7 +120,7 @@ #define RMOBJ_RM_MULT_OBJS 4563866 #define RMOBJ_REPLACE_WEAPON 4658526 #define RMOBJ_THROW 4266040 -#define RMOBJ_SUB_CONTAINER 4683191 // search and remove the item from a container inside of an inventory +#define RMOBJ_SUB_CONTAINER 4683191 // search and remove the item from nested containers in the inventory // common prototype offsets for get/set_proto_data #define PROTO_PID (1) @@ -385,10 +385,11 @@ #define OBJ_DATA_FID (0x20) #define OBJ_DATA_ELEVATION (0x28) #define OBJ_DATA_PID (0x64) -#define OBJ_DATA_CID (0x68) // combat id, used for savegame -#define OBJ_DATA_SID (0x78) +#define OBJ_DATA_CID (0x68) // combat ID, used for savegame +#define OBJ_DATA_SID (0x78) // script ID +#define OBJ_DATA_SCRIPT_INDEX (0x80) // script index number in scripts.lst // items -#define OBJ_DATA_CUR_CHARGES (0x3C) +#define OBJ_DATA_CUR_CHARGES (0x3C) // for key items it's the key code // critters #define OBJ_DATA_COMBAT_STATE (0x3C) // flags: 1 - combat, 2 - target is out of range, 4 - flee #define OBJ_DATA_CUR_ACTION_POINT (0x40) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index b684946f..490d995a 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -250,6 +250,7 @@ // sfall_funcX macros #define add_extra_msg_file(name) sfall_func1("add_extra_msg_file", name) #define add_iface_tag sfall_func0("add_iface_tag") +#define add_trait(traitId) sfall_func1("add_trait", traitId) #define art_cache_clear sfall_func0("art_cache_clear") #define attack_is_aimed sfall_func0("attack_is_aimed") #define car_gas_amount sfall_func0("car_gas_amount") @@ -259,8 +260,8 @@ #define dialog_message(text) sfall_func1("dialog_message", text) #define dialog_obj sfall_func0("dialog_obj") #define display_stats sfall_func0("display_stats") -#define draw_image(frmFile, frame, x, y, noTransparent) sfall_func5("draw_image", frmFile, frame, x, y, noTransparent) -#define draw_image_scaled(frmFile, frame, x, y, w, h) sfall_func6("draw_image_scaled", frmFile, frame, x, y, w, h) +#define draw_image(pathFile, frame, x, y, noTrans) sfall_func5("draw_image", pathFile, frame, x, y, noTrans) +#define draw_image_scaled(pathFile, frame, x, y, w, h) sfall_func6("draw_image_scaled", pathFile, frame, x, y, w, h) #define exec_map_update_scripts sfall_func0("exec_map_update_scripts") #define floor2(value) sfall_func1("floor2", value) #define get_can_rest_on_map(map, elev) sfall_func2("get_can_rest_on_map", map, elev) @@ -269,6 +270,7 @@ #define get_flags(obj) sfall_func1("get_flags", obj) #define get_ini_section(file, sect) sfall_func2("get_ini_section", file, sect) #define get_ini_sections(file) sfall_func1("get_ini_sections", file) +#define get_inven_ap_cost sfall_func0("get_inven_ap_cost") #define get_map_enter_position sfall_func0("get_map_enter_position") #define get_metarule_table sfall_func0("get_metarule_table") #define get_object_ai_data(obj, aiParam) sfall_func2("get_object_ai_data", obj, aiParam) @@ -296,8 +298,6 @@ #define set_cursor_mode(mode) sfall_func1("set_cursor_mode", mode) #define set_drugs_data(type, pid, value) sfall_func3("set_drugs_data", type, pid, value) #define set_dude_obj(critter) sfall_func1("set_dude_obj", critter) -#define set_fake_perk_npc(npc, perk, level, image, desc) sfall_func5("set_fake_perk_npc", npc, perk, level, image, desc) -#define set_fake_trait_npc(npc, trait, active, image, desc) sfall_func5("set_fake_trait_npc", npc, trait, active, image, desc) #define set_flags(obj, flags) sfall_func2("set_flags", obj, flags) #define set_iface_tag_text(tag, text, color) sfall_func3("set_iface_tag_text", tag, text, color) #define set_ini_setting(setting, value) sfall_func2("set_ini_setting", setting, value) @@ -306,10 +306,14 @@ #define set_outline(obj, color) sfall_func2("set_outline", obj, color) #define set_rest_heal_time(time) sfall_func1("set_rest_heal_time", time) #define set_rest_mode(mode) sfall_func1("set_rest_mode", mode) -#define set_selectable_perk_npc(npc, perk, active, image, desc) sfall_func5("set_selectable_perk_npc", npc, perk, active, image, desc) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) #define unset_unique_id(obj) sfall_func2("set_unique_id", obj, -1) #define set_unjam_locks_time(time) sfall_func1("set_unjam_locks_time", time) #define spatial_radius(obj) sfall_func1("spatial_radius", obj) #define tile_refresh_display sfall_func0("tile_refresh_display") #define unjam_lock(obj) sfall_func1("unjam_lock", obj) +#define unwield_slot(critter, slot) sfall_func2("unwield_slot", critter, slot) + +#define set_fake_perk_npc(npc, perk, level, image, desc) sfall_func5("set_fake_perk_npc", npc, perk, level, image, desc) +#define set_fake_trait_npc(npc, trait, active, image, desc) sfall_func5("set_fake_trait_npc", npc, trait, active, image, desc) +#define set_selectable_perk_npc(npc, perk, active, image, desc) sfall_func5("set_selectable_perk_npc", npc, perk, active, image, desc) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index c932355b..9e601a0d 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -431,7 +431,7 @@ int ret1 - Override setting (-1 - use engine handler, any other value - prev HOOK_INVENWIELD (hs_invenwield.int) -Runs before wielding or unwielding an armor or a weapon by a critter (except when using inventory by PC). +Runs before causing a critter or the player to wield/unwield an armor or a weapon (except when using the inventory by PC). An example usage would be to change critter art depending on armor being used or to dynamically customize weapon animations. NOTE: when replacing a previously wielded armor or weapon, the unwielding hook will not be executed. If you need to rely on this, try checking if armor/weapon is already equipped when wielding hook is executed. @@ -440,6 +440,7 @@ Critter arg1 - critter Obj arg2 - item being wielded or unwielded (weapon/armor) int arg3 - slot (INVEN_TYPE_*) int arg4 - 1 when wielding, 0 when unwielding +int arg5 - 1 when removing an equipped item from inventory, 0 otherwise int ret1 - overrides hard-coded handler (-1 - use engine handler, any other value - override) - NOT RECOMMENDED diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 707f3c29..52132f1c 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -622,6 +622,17 @@ optional argument: - fileNumber: the file ID number for the message_str_game function. The available range is from 0x2000 to 0x2FFF (see ExtraGameMsgFileList setting in ddraw.ini) use fileNumber only if you want to add a message file without editing ddraw.ini or existing scripts to support the old way +> void sfall_func2("unwield_slot", object critter, int slot) +- unequips an item from the specified slot for a critter or the player +- can take off player's equipped item when the inventory is opened, or the player is in the barter screen +- slot: 0 - armor slot, 1 - right slot, 2 - left slot (see INVEN_TYPE_* in define.h) + +> void sfall_func1("add_trait", int traitId) +- adds the specified trait to the player + +> int sfall_func0("get_inven_ap_cost") +- returns the current AP cost to access the inventory in combat + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/artifacts/sfall.dat b/artifacts/sfall.dat index fac95545..0d7c96fa 100644 Binary files a/artifacts/sfall.dat and b/artifacts/sfall.dat differ diff --git a/sfall/Modules/CRC.cpp b/sfall/CRC.cpp similarity index 90% rename from sfall/Modules/CRC.cpp rename to sfall/CRC.cpp index 6def3f36..82f94616 100644 --- a/sfall/Modules/CRC.cpp +++ b/sfall/CRC.cpp @@ -19,9 +19,9 @@ #include #include -#include "..\main.h" -#include "..\Version.h" -#include "..\Logging.h" +#include "main.h" +#include "Version.h" +#include "Logging.h" #include "CRC.h" @@ -31,7 +31,7 @@ namespace sfall static const DWORD ExpectedSize = 0x00122800; static const DWORD ExpectedCRC[] = {0xe1680293, 0xef34f989}; -static void inline Fail(const char* a) { MessageBoxA(0, a, "Error", MB_TASKMODAL); ExitProcess(1); } +static void inline Fail(const char* a) { MessageBoxA(0, a, "Error", MB_TASKMODAL | MB_ICONERROR); ExitProcess(1); } static const DWORD CRC_table[256] = { 0x00000000, 0x0A5F4D75, 0x14BE9AEA, 0x1EE1D79F, 0x14C5EB57, 0x1E9AA622, 0x007B71BD, 0x0A243CC8, @@ -79,16 +79,18 @@ static DWORD crcInternal(BYTE* data, DWORD size) { void CRC(const char* filepath) { char buf[512]; HANDLE h = CreateFileA(filepath, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); - if (h == INVALID_HANDLE_VALUE) Fail("Cannot open fallout2.exe for CRC check"); + if (h == INVALID_HANDLE_VALUE) Fail("Cannot open fallout2.exe for CRC check."); DWORD size = GetFileSize(h, 0), crc; bool sizeMatch = (size == ExpectedSize); - if (!sizeMatch && GetPrivateProfileIntA("Debugging", "SkipSizeCheck", 0, ::sfall::ddrawIni)) { + if (!sizeMatch && iniGetInt("Debugging", "SkipSizeCheck", 0, ::sfall::ddrawIni)) { sizeMatch = true; } if (!sizeMatch) { - sprintf_s(buf, "You're trying to use sfall with an incompatible version of fallout\nWas expecting '" TARGETVERSION "'\n\nfallout2.exe was an unexpected size. Expected 0x%x but got 0x%x", ExpectedSize, size); + sprintf_s(buf, "You're trying to use sfall with an incompatible version of Fallout.\n" + "Was expecting '" TARGETVERSION "'.\n\n" + "fallout2.exe has an unexpected size. Expected %d bytes but got %d bytes.", ExpectedSize, size); Fail(buf); } BYTE* bytes = new BYTE[size]; @@ -98,7 +100,7 @@ void CRC(const char* filepath) { bool matchedCRC = false; auto extraCrcList = GetIniList("Debugging", "ExtraCRC", "", 512, ',', ::sfall::ddrawIni); - if (extraCrcList.size() > 0) { + if (!extraCrcList.empty()) { matchedCRC = std::any_of(extraCrcList.begin(), extraCrcList.end(), [crc](const std::string& testCrcStr) { auto testedCrc = strtoul(testCrcStr.c_str(), 0, 16); return testedCrc && crc == testedCrc; @@ -109,7 +111,9 @@ void CRC(const char* filepath) { if (crc == ExpectedCRC[i]) matchedCRC = true; } if (!matchedCRC) { - sprintf_s(buf, "You're trying to use sfall with an incompatible version of fallout\nWas expecting '" TARGETVERSION "'\n\nfallout2.exe had an unexpected crc. Expected 0x%x but got 0x%x", ExpectedCRC[0], crc); + sprintf_s(buf, "You're trying to use sfall with an incompatible version of Fallout.\n" + "Was expecting '" TARGETVERSION "'.\n\n" + "fallout2.exe has an unexpected CRC. Expected 0x%x but got 0x%x.", ExpectedCRC[0], crc); Fail(buf); } diff --git a/sfall/Modules/CRC.h b/sfall/CRC.h similarity index 100% rename from sfall/Modules/CRC.h rename to sfall/CRC.h diff --git a/sfall/CheckAddress.cpp b/sfall/CheckAddress.cpp new file mode 100644 index 00000000..fad4128d --- /dev/null +++ b/sfall/CheckAddress.cpp @@ -0,0 +1,171 @@ +#include "main.h" + +#include "CheckAddress.h" + +namespace sfall +{ + +#ifndef NDEBUG + +std::multimap writeAddress; + +static std::vector excludeAddr = { + 0x42a14f, 0x42a178, +}; + +struct HackPair { + long addr; + long len; + + HackPair(long addr, long len) : addr(addr), len(len) {} +}; + +static std::vector hackAddr = { + // scripting: Anims + {0x459C97, 1}, {0x459D4B, 1}, {0x459E3B, 1}, {0x459EEB, 1}, {0x459F9F, 1}, {0x45A053, 1}, {0x45A10B, 1}, {0x45AE53, 1}, + // scripting: Misc + {0x495C50, 4}, {0x495C77, 6}, {0x495CEC, 6}, {0x495CFB, 1}, {0x495BF1, 5}, {0x4AFBC1, 1}, {0x4AFC1C, 2}, + // scripting: Perks + {0x424AB6, 1}, {0x4AFAE2, 4}, + {0x49687F, 1}, {0x496880, 4}, // hookcall + // scripting: Stats + {0x43C27A, 1}, {0x4AFABD, 5}, + // scripting: Worldmap + {0x4C06D1, 5}, + + // module: AI + {0x4290B6, 5}, + // module: BarBoxes + {0x461342, 5}, {0x461243, 5}, + {0x461493, 5}, //{0x461495, 1}, + {0x461513, 1}, {0x461718, 1}, {0x46170A, 1}, + // module: Books + {0x49B9FB, 5}, + // module: Combat + {0x478EC6, 1}, {0x478EC7, 4}, // hookcall + // module: CritterStats + {0x4AF761, 1}, {0x4AF762, 4}, // hookcall + // module: DamageMod + {0x4722DD, 1}, {0x4722DE, 4}, {0x472309, 1}, {0x47230A, 4}, // hookcalls + // module: Debug + {0x48007E, 1}, + // module: Drugs + {0x479EEC, 5}, {0x43C15C, 5}, {0x47A5B8, 5}, {0x47A50C, 5}, + {0x47A523, 4}, {0x47A527, 1}, {0x47A3A8, 1}, + // module: Explosions + {0x49BCC7, 5}, {0x49BD56, 5}, {0x4A2865, 5}, {0x4737F2, 5}, {0x49C005, 5}, + {0x411B54, 1}, {0x423C93, 1}, + {0x411A19, 4}, {0x411A29, 4}, {0x411A35, 4}, {0x411A3C, 4}, + {0x479183, 4}, {0x47918B, 4}, + {0x411709, 1}, {0x4119FC, 1}, {0x411C08, 1}, {0x4517C1, 1}, {0x423BC8, 1}, {0x42381A, 1}, + // module: ExtraSave + //{0x47B923, 6}, + // module: Inventory + // module: Interface + {0x460BB6, 4}, + // module: LoadOrder + {0x43FA9F, 4}, {0x44EB5B, 4}, {0x48152E, 4}, + //{0x480A95, 5}, (it in movies.cpp) + // module: MainMenu + {0x481753, 4}, {0x48175C, 4}, {0x481748, 4}, + // module: Message + {0x484B18, 1}, {0x484B19, 4}, // hookcall + // module: Movies + {0x44E937, 1}, {0x44E938, 4}, {0x44E949, 1}, {0x44E94A, 4}, // hookcalls + // module: Objects + {0x4A364A, 5}, {0x4831D9, 1}, {0x4831DA, 1}, + {0x4841D6, 1}, {0x4841D7, 4}, // hookcall + // module: PartyControl + {0x422BDE, 1}, {0x4229EC, 1}, + {0x49EB09, 1}, {0x49EB0A, 4}, {0x458242, 1}, {0x458243, 4}, {0x458326, 1}, {0x458327, 4}, // hookcalls + // module: Perks + {0x43C77C, 1}, {0x43C77D, 4}, // hookcall + {0x478AC4, 1}, {0x478AC5, 4}, {0x496823, 5}, + {0x496C2D, 1}, {0x496C2E, 4}, {0x496D11, 1}, {0x496D12, 4}, // hookcalls + // trait hookcalls + {0x4245E0, 1}, {0x4245E1, 4}, {0x4248F9, 1}, {0x4248FA, 4}, {0x478C8A, 1}, {0x478C8B, 4}, + {0x478E70, 1}, {0x478E71, 4}, {0x410707, 1}, {0x410708, 4}, {0x42389F, 1}, {0x4238A0, 4}, + {0x47A0CD, 1}, {0x47A0CE, 4}, {0x47A51A, 1}, {0x47A51B, 4}, {0x479BE1, 1}, {0x479BE2, 4}, + {0x47A0DD, 1}, {0x47A0DE, 4}, {0x43C295, 1}, {0x43C296, 4}, {0x43C2F3, 1}, {0x43C2F4, 4}, + {0x43C2A4, 1}, {0x43C2A5, 4}, {0x44272E, 1}, {0x44272F, 4}, + // end + {0x4B3C7C, 5}, {0x4B40FC, 5}, + {0x4B3A81, 4}, {0x4B3B80, 4}, {0x4B3AAE, 4}, {0x4B3BA0, 4}, {0x4B3BC0, 4}, + {0x478E75, 5}, + {0x42448E, 4}, {0x424489, 4}, {0x424474, 4}, + {0x42465D, 1}, {0x424636, 1}, {0x4251CE, 1}, + // module: Player model + {0x418B88, 4}, {0x418BAB, 4}, {0x418B50, 4}, {0x418B6D, 4}, + // module: Skills + {0x4AA59D, 5}, {0x4AA738, 5}, {0x4AA940, 5}, + {0x4AA9E1, 1}, {0x4AA9E2, 4}, {0x4AA9F1, 1}, {0x4AA9F2, 4}, {0x4AA9EC, 1}, {0x4AA9ED, 4}, // hookcalls + // module: Stats + {0x4AF6FC, 5}, + // module: Worldmap + {0x4A33B8, 6}, {0x4A34EC, 1}, {0x4A3544, 1}, + {0x42E57A, 1}, {0x42E57B, 4}, // hookcall + {0x4C2D9B, 4}, {0x499FDE, 4}, + {0x49952C, 1}, {0x497557, 1}, {0x42E587, 1}, {0x42E588, 4}, {0x499FD4, 2}, {0x499E93, 2}, + // module: MiscPatches + {0x41276A, 1}, {0x480AAA, 4}, {0x444BA5, 4}, {0x444BCA, 4}, +}; + +//static std::vector jumpAddr = { +//}; + +// Checking for conflicts requires all options in ddraw.ini to be enabled +void PrintAddrList() { + long level = iniGetInt("Debugging", "Enable", 0, ::sfall::ddrawIni); + if (level < 10) hackAddr.clear(); + + std::vector sortAddr(hackAddr); + + for (const auto &wa : writeAddress) { + sortAddr.emplace_back(wa.first, wa.second); + } + std::sort(sortAddr.begin(), sortAddr.end(), [](const HackPair &a, const HackPair &b) { + return (a.addr != b.addr) ? a.addr < b.addr : a.len < b.len; + }); + + unsigned long pa = 0, pl = 0; + for (const auto &el : sortAddr) + { + unsigned long diff = (pa) ? (el.addr - pa) : -1; // length between two addresses + if (diff == 0) { + dlog_f("0x%x L:%d [Overwriting]\n", DL_MAIN, el.addr, el.len); + } else if (diff < pl) { + dlog_f("0x%x L:%d [Conflict] with 0x%x L:%d\n", DL_MAIN, el.addr, el.len, pa, pl); + MessageBoxA(0, "Conflict detected!", "", MB_TASKMODAL); + } else if (level >= 11 && diff == pl) { + dlog_f("0x%x L:%d [Warning] Hacking near:0x%x\n", DL_MAIN, el.addr, el.len, pa); + } else if (level >= 12) { + dlog_f("0x%x L:%d\n", DL_MAIN, el.addr, el.len); + } + pa = el.addr; + pl = el.len; + } +} + +void CheckConflict(DWORD addr, long len) { + if (writeAddress.find(addr) != writeAddress.cend()) { + if (std::find(excludeAddr.cbegin(), excludeAddr.cend(), addr) != excludeAddr.cend()) return; + char buf[64]; + sprintf_s(buf, "Memory overwriting at address 0x%x", addr); + MessageBoxA(0, buf, "", MB_TASKMODAL); + } + writeAddress.emplace(addr, len); +} + +void AddrAddToList(DWORD addr, long len) { + writeAddress.emplace(addr, len); +} + +#else + +void PrintAddrList() {} +void CheckConflict(DWORD addr, long len) {} +void AddrAddToList(DWORD addr, long len) {} + +#endif + +} diff --git a/sfall/CheckAddress.h b/sfall/CheckAddress.h new file mode 100644 index 00000000..c621dcdd --- /dev/null +++ b/sfall/CheckAddress.h @@ -0,0 +1,10 @@ +#pragma once + +namespace sfall +{ + +void PrintAddrList(); +void CheckConflict(DWORD addr, long len); +void AddrAddToList(DWORD addr, long len); + +} diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index dea4340f..17e7c3dc 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -17,6 +17,7 @@ */ #include +#include #include "Functions.h" #include "FunctionOffsets.h" @@ -64,23 +65,22 @@ Proto* GetProto(long pid) { bool CritterCopyProto(long pid, long* &proto_dst) { fo::Proto* protoPtr; - if (fo::func::proto_ptr(pid, &protoPtr) == -1) return false; - /*if (!proto_dst)*/ proto_dst = new long[104]; + if (fo::func::proto_ptr(pid, &protoPtr) == -1) { + proto_dst = nullptr; + return false; + } + proto_dst = reinterpret_cast(new int32_t[104]); memcpy(proto_dst, protoPtr, 416); return true; } void SkillGetTags(long* result, long num) { - if (num > 4) { - num = 4; - } + if (num > 4) num = 4; fo::func::skill_get_tags(result, num); } void SkillSetTags(long* tags, long num) { - if (num > 4) { - num = 4; - } + if (num > 4) num = 4; fo::func::skill_set_tags(tags, num); } diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index 1c12b348..85ea645a 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -17,6 +17,7 @@ */ #include "FunctionOffsets.h" +#include "VariableOffsets.h" #include "Functions.h" @@ -25,6 +26,15 @@ namespace fo namespace func { +// prints debug message to debug.log file for develop build +#ifndef NDEBUG +void __declspec(naked) dev_printf(const char* fmt, ...) { + __asm jmp fo::funcoffs::debug_printf_; +} +#else +void dev_printf(const char* fmt, ...) {} +#endif + // Fallout2.exe was compiled using WATCOM compiler, which uses Watcom register calling convention. // In this convention, up to 4 arguments are passed via registers in this order: EAX, EDX, EBX, ECX. @@ -455,7 +465,21 @@ void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_h } } +//void __declspec(naked) __stdcall buf_to_buf(void* to_buf, long to_width, void* from_buf, long from_width, long width, long height) { +// __asm jmp fo::funcoffs::srcCopy_; +//} +long __fastcall get_game_config_string(const char* outValue, const char* section, const char* param) { + __asm { + mov ebx, param; + mov eax, FO_VAR_game_config; + call fo::funcoffs::config_get_string_; // section, outValue + } +} + +//////////////////////////////////// +// X-Macro for wrapper functions. // +//////////////////////////////////// #define WRAP_WATCOM_FUNC0(retType, name) \ retType __stdcall name() { \ WRAP_WATCOM_CALL0(name##_) \ diff --git a/sfall/FalloutEngine/Functions.h b/sfall/FalloutEngine/Functions.h index f377fbf7..f4488f6f 100644 --- a/sfall/FalloutEngine/Functions.h +++ b/sfall/FalloutEngine/Functions.h @@ -29,6 +29,9 @@ namespace fo namespace func { +// Prints debug message to debug.log file for develop build +void dev_printf(const char* fmt, ...); + /* Add functions here if they have non-trivial wrapper implementation (like vararg functions or too many arguments, etc.) Otherwise use Functions_def.h file (much easier). @@ -207,9 +210,11 @@ void __fastcall displayInWindow(long w_here, long width, long height, void* data void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_height, long xy_shift, long w_width, void* data); +//void __declspec() __stdcall buf_to_buf(void* to_buf, long to_width, void* from_buf, long from_width, long width, long height); + +long __fastcall get_game_config_string(const char* outValue, const char* section, const char* param); // X-Macro for wrapper functions. - #define WRAP_WATCOM_FUNC0(retType, name) \ retType __stdcall name(); diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index b31ba0c5..68c22576 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -17,10 +17,13 @@ // For functions that have 3 or more arguments, it is preferable to use the fastcall calling convention // because the compiler builds the better/optimized code when calling the engine functions WRAP_WATCOM_FFUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count) +WRAP_WATCOM_FFUNC3(void, correctFidForRemovedItem, GameObject*, critter, GameObject*, item, long, slotFlag) WRAP_WATCOM_FFUNC7(long, createWindow, const char*, winName, long, x, long, y, long, width, long, height, long, bgColorIndex, long, flags) WRAP_WATCOM_FFUNC3(void, display_inventory, long, inventoryOffset, long, visibleOffset, long, mode) WRAP_WATCOM_FFUNC4(void, display_target_inventory, long, inventoryOffset, long, visibleOffset, DWORD*, targetInventory, long, mode) WRAP_WATCOM_FFUNC3(FrmFrameData*, frame_ptr, FrmHeaderData*, frm, long, frame, long, direction) +WRAP_WATCOM_FFUNC3(void, intface_update_items, long, animate, long, modeLeft,long, modeRight) +WRAP_WATCOM_FFUNC3(long, item_add_force, GameObject*, critter, GameObject*, item, long, count) WRAP_WATCOM_FFUNC7(void, make_straight_path_func, GameObject*, objFrom, DWORD, tileFrom, DWORD, tileTo, void*, rotationPtr, DWORD*, result, long, flags, void*, func) WRAP_WATCOM_FFUNC3(GameObject*, obj_blocking_at, GameObject*, object, long, tile, long, elevation) WRAP_WATCOM_FFUNC3(long, object_under_mouse, long, crSwitch, long, inclDude, long, elevation) @@ -65,7 +68,9 @@ WRAP_WATCOM_FUNC0(long, intface_is_item_right_hand) // redraws the main game interface windows (useful after changing some data like active hand, etc.) WRAP_WATCOM_FUNC0(void, intface_redraw) WRAP_WATCOM_FUNC0(void, intface_toggle_item_state) +WRAP_WATCOM_FUNC1(void, intface_update_ac, long, animate) WRAP_WATCOM_FUNC0(void, intface_use_item) +WRAP_WATCOM_FUNC2(long, inven_unwield, GameObject*, critter, long, slot) WRAP_WATCOM_FUNC0(long, is_pc_sneak_working) WRAP_WATCOM_FUNC1(long, item_c_curr_size, GameObject*, critter) WRAP_WATCOM_FUNC1(long, item_caps_total, GameObject*, object) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index e5c3065a..0fec931c 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -199,17 +199,18 @@ struct ScriptInstance { struct Program { const char* fileName; long *codeStackPtr; - char gap_8[8]; + long gap_8; + long gap_9; long *codePtr; long field_14; - char gap_18[4]; + long gap_18; long *dStackPtr; long *aStackPtr; long *dStackOffs; long *aStackOffs; - char gap_2C[4]; + long gap_2C; long *stringRefPtr; - char gap_34[4]; + long gap_34; long *procTablePtr; }; @@ -519,8 +520,8 @@ struct Proto { // for each DamageType long damageThreshold[7]; long perk; - long maleFrameNum; - long femaleFrameNum; + long maleFID; + long femaleFID; }; struct Container { diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index 448418ce..d8b9237b 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -71,7 +71,9 @@ #define FO_VAR_frame_time 0x5709C4 #define FO_VAR_free_perk 0x570A29 #define FO_VAR_frstc_draw1 0x5707D8 +#define FO_VAR_game_config 0x58E950 #define FO_VAR_game_global_vars 0x5186C0 +#define FO_VAR_game_ui_disabled 0x5186B4 #define FO_VAR_game_user_wants_to_quit 0x5186CC #define FO_VAR_gcsd 0x51094C #define FO_VAR_gdBarterMod 0x51873C diff --git a/sfall/Logging.cpp b/sfall/Logging.cpp index 565c839f..4ea2dbc6 100644 --- a/sfall/Logging.cpp +++ b/sfall/Logging.cpp @@ -58,16 +58,16 @@ void dlog_f(const char *fmt, int type, ...) { void LoggingInit() { Log.open("sfall-log.txt", ios_base::out | ios_base::trunc); - if (GetPrivateProfileIntA("Debugging", "Init", 0, ::sfall::ddrawIni)) { + if (iniGetInt("Debugging", "Init", 0, ::sfall::ddrawIni)) { DebugTypes |= DL_INIT; } - if (GetPrivateProfileIntA("Debugging", "Hook", 0, ::sfall::ddrawIni)) { + if (iniGetInt("Debugging", "Hook", 0, ::sfall::ddrawIni)) { DebugTypes |= DL_HOOK; } - if (GetPrivateProfileIntA("Debugging", "Script", 0, ::sfall::ddrawIni)) { + if (iniGetInt("Debugging", "Script", 0, ::sfall::ddrawIni)) { DebugTypes |= DL_SCRIPT; } - if (GetPrivateProfileIntA("Debugging", "Criticals", 0, ::sfall::ddrawIni)) { + if (iniGetInt("Debugging", "Criticals", 0, ::sfall::ddrawIni)) { DebugTypes |= DL_CRITICALS; } } diff --git a/sfall/Modules/AnimationsAtOnceLimit.cpp b/sfall/Modules/Animations.cpp similarity index 99% rename from sfall/Modules/AnimationsAtOnceLimit.cpp rename to sfall/Modules/Animations.cpp index 13a9fef4..36f21331 100644 --- a/sfall/Modules/AnimationsAtOnceLimit.cpp +++ b/sfall/Modules/Animations.cpp @@ -20,7 +20,7 @@ #include "..\FalloutEngine\Fallout2.h" #include "LoadGameHook.h" -#include "AnimationsAtOnceLimit.h" +#include "Animations.h" namespace sfall { @@ -325,7 +325,7 @@ void ApplyAnimationsAtOncePatches(signed char aniMax) { SafeWriteBatch(40 + sadAddr, sad_28); } -void AnimationsAtOnce::init() { +void Animations::init() { animationLimit = GetConfigInt("Misc", "AnimationsAtOnceLimit", 32); if (animationLimit > 32) { if (animationLimit > 127) { @@ -345,7 +345,7 @@ void AnimationsAtOnce::init() { HookCall(0x411E1F, action_climb_ladder_hook); } -void AnimationsAtOnce::exit() { +void Animations::exit() { } } diff --git a/sfall/Modules/AnimationsAtOnceLimit.h b/sfall/Modules/Animations.h similarity index 90% rename from sfall/Modules/AnimationsAtOnceLimit.h rename to sfall/Modules/Animations.h index f7636b1d..03383dc1 100644 --- a/sfall/Modules/AnimationsAtOnceLimit.h +++ b/sfall/Modules/Animations.h @@ -23,9 +23,9 @@ namespace sfall { -class AnimationsAtOnce : public Module { +class Animations : public Module { public: - const char* name() { return "AnimationsAtOnceLimit"; } + const char* name() { return "Animations"; } void init(); void exit() override; }; diff --git a/sfall/Modules/BarBoxes.cpp b/sfall/Modules/BarBoxes.cpp index 4a3385f6..8aefbcf2 100644 --- a/sfall/Modules/BarBoxes.cpp +++ b/sfall/Modules/BarBoxes.cpp @@ -146,6 +146,7 @@ static void ReconstructBarBoxes(int count) { } } +//static BYTE restoreData[] = {0x31, 0xD2, 0x89, 0x94, 0x24}; // xor edx, edx; mov... static void ResetBoxes() { for (int i = 0; i < actualBoxCount; i++) { boxText[i].isActive = false; @@ -158,8 +159,7 @@ static void ResetBoxes() { setBoxIndex = 5; // set start //SafeWrite32(0x461343, 0x00023D05); // call getmsg_ ReconstructBarBoxes(totalBoxCount); - //SafeWrite8(0x461243, 0x31); - //SafeWrite32(0x461244, 0x249489D2); + //SafeWriteBytes(0x461243, restoreData, 5); } void BarBoxes::SetText(int box, const char* text, DWORD color) { @@ -258,7 +258,6 @@ static void __declspec(naked) refresh_box_bar_win_hack() { } void BarBoxes::init() { - initCount += GetConfigInt("Misc", "BoxBarCount", 5); if (initCount < 5) initCount = 5; // exclude the influence of negative values from the config if (initCount > 100) initCount = 100; @@ -300,7 +299,7 @@ void BarBoxes::init() { MakeCall(0x4615FA, refresh_box_bar_win_hack); SafeWriteBatch((DWORD)newBoxSlot, bboxSlotAddr); // _bboxslot - ifaceWidth = GetPrivateProfileIntA("IFACE", "IFACE_BAR_WIDTH", 640, ".\\f2_res.ini"); + ifaceWidth = iniGetInt("IFACE", "IFACE_BAR_WIDTH", 640, ".\\f2_res.ini"); if (ifaceWidth < 640) ifaceWidth = 640; LoadGameHook::OnAfterGameInit() += SetMaxSlots; diff --git a/sfall/Modules/BarBoxes.h b/sfall/Modules/BarBoxes.h index 078f2f66..6fff8be0 100644 --- a/sfall/Modules/BarBoxes.h +++ b/sfall/Modules/BarBoxes.h @@ -1,3 +1,5 @@ +#pragma once + #include "Module.h" namespace sfall diff --git a/sfall/Modules/Books.cpp b/sfall/Modules/Books.cpp index bc2982c5..d267d54d 100644 --- a/sfall/Modules/Books.cpp +++ b/sfall/Modules/Books.cpp @@ -89,10 +89,10 @@ void Books::init() { dlog("Applying books patch...", DL_INIT); const char* iniBooks = booksFile.insert(0, ".\\").c_str(); - bool includeVanilla = (GetPrivateProfileIntA("main", "overrideVanilla", 0, iniBooks) == 0); + bool includeVanilla = (iniGetInt("main", "overrideVanilla", 0, iniBooks) == 0); if (includeVanilla) BooksCount = 5; - int count = GetPrivateProfileIntA("main", "count", 0, iniBooks); + int count = iniGetInt("main", "count", 0, iniBooks); int n = 0; if (count > 0) { @@ -104,9 +104,9 @@ void Books::init() { char section[4]; for (int i = 1; i <= count; i++) { _itoa_s(i, section, 10); - if (books[BooksCount].bookPid = GetPrivateProfileIntA(section, "PID", 0, iniBooks)) { - books[BooksCount].msgID = GetPrivateProfileIntA(section, "TextID", 0, iniBooks); - books[BooksCount].skill = GetPrivateProfileIntA(section, "Skill", 0, iniBooks); + if (books[BooksCount].bookPid = iniGetInt(section, "PID", 0, iniBooks)) { + books[BooksCount].msgID = iniGetInt(section, "TextID", 0, iniBooks); + books[BooksCount].skill = iniGetInt(section, "Skill", 0, iniBooks); BooksCount++; n++; } diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 2fd96a3c..f1d85247 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -1,5 +1,6 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "HookScripts\InventoryHs.h" #include "Drugs.h" #include "LoadGameHook.h" #include "ScriptExtender.h" @@ -10,6 +11,7 @@ namespace sfall { using namespace fo; using namespace Fields; +using namespace ObjectFlag; static DWORD critterBody = 0; static DWORD sizeOnBody = 0; @@ -17,6 +19,10 @@ static DWORD weightOnBody = 0; static char textBuf[355]; +/* + Saving a list of PIDs for saved drug effects + Note: the sequence of saving and loading the list is very important! +*/ std::list drugsPid; static void __fastcall DrugPidPush(int pid) { @@ -54,6 +60,7 @@ bool BugFixes::DrugsLoadFix(HANDLE file) { } return false; } +/////////////////////////////////////////////////////////////////////////////// void ResetBodyState() { __asm mov critterBody, 0; @@ -380,33 +387,105 @@ static void __declspec(naked) queue_clear_type_mem_free_hook() { } } +static void __declspec(naked) partyMemberCopyLevelInfo_hack() { + __asm { + mov eax, esi; // source + xor ecx, ecx; // animation + mov ebx, 1; // right slot + retn; + } +} + static void __declspec(naked) partyMemberCopyLevelInfo_hook_stat_level() { __asm { nextArmor: - mov eax, esi - call fo::funcoffs::inven_worn_ - test eax, eax - jz noArmor - and byte ptr [eax+0x27], 0xFB // Unset the flag of equipped armor - jmp nextArmor + mov eax, esi; + call fo::funcoffs::inven_worn_; + test eax, eax; + jz noArmor; + and byte ptr [eax][flags + 3], ~Worn >> 24; // Unset flag of equipped armor + jmp nextArmor; noArmor: - mov eax, esi - jmp fo::funcoffs::stat_level_ + mov eax, esi; + jmp fo::funcoffs::stat_level_; } } static void __declspec(naked) correctFidForRemovedItem_hook_adjust_ac() { __asm { - call fo::funcoffs::adjust_ac_ + call fo::funcoffs::adjust_ac_; nextArmor: - mov eax, esi - call fo::funcoffs::inven_worn_ - test eax, eax - jz end - and byte ptr [eax+0x27], 0xFB // Unset flag of equipped armor - jmp nextArmor + mov eax, esi; + call fo::funcoffs::inven_worn_; + test eax, eax; + jz end; + and byte ptr [eax][flags + 3], ~Worn >> 24; // Unset flag of equipped armor + jmp nextArmor; end: - retn + retn; + } +} + +static void __declspec(naked) op_move_obj_inven_to_obj_hook() { + __asm { + jz skip; // source == dude + mov eax, edx; + call fo::funcoffs::isPartyMember_; + test eax, eax; // is source a party member? + jnz skip; + retn; // must be eax = 0 +skip: + mov eax, edx; + call fo::funcoffs::inven_worn_; + cmp edx, ecx; // source(edx) == dude(ecx) + jz dudeFix; + test eax, eax; + jz end; + // fix for party member + call InvenUnwield_HookMove; // run HOOK_INVENWIELD before moving item + push ebx; + mov ecx, edx; + xor ebx, ebx; // new armor + xchg eax, edx; // set: eax - source, edx - removed armor + call fo::funcoffs::adjust_ac_; + mov edx, ecx; + pop ebx; + xor eax, eax; +end: + retn; // must be eax = 0 +dudeFix: + test eax, eax; + jz equipped; // no armor + // additionally check flag of equipped armor for dude + test byte ptr [eax][flags + 3], Worn >> 24; + jnz equipped; + xor eax, eax; +equipped: + or cl, 1; // reset ZF + retn; + } +} + +static void __declspec(naked) obj_drop_hook() { + __asm { + test byte ptr [edx][flags + 3], (Worn | Right_Hand | Left_Hand) >> 24; + jz skipHook; + call InvenUnwield_HookDrop; // run HOOK_INVENWIELD before dropping item +skipHook: + test byte ptr [edx][flags + 3], Worn >> 24; + jnz fixArmorStat; + jmp fo::funcoffs::obj_remove_from_inven_; +fixArmorStat: + call fo::funcoffs::isPartyMember_; // and dude + test eax, eax; + jz skip; + mov eax, ecx; + xor ebx, ebx; // new armor + call fo::funcoffs::adjust_ac_; // eax - source, edx - removed armor + mov edx, esi; +skip: + mov eax, ecx; + jmp fo::funcoffs::obj_remove_from_inven_; } } @@ -817,6 +896,25 @@ static void __declspec(naked) op_wield_obj_critter_adjust_ac_hook() { } } +static const DWORD partyMember_init_End = 0x493D16; +static void __declspec(naked) NPCStage6Fix1() { + __asm { + imul eax, edx, 204; // multiply record size 204 bytes by number of NPC records in party.txt + mov ebx, eax; // copy total record size for later memset + call fo::funcoffs::mem_malloc_; // malloc the necessary memory + jmp partyMember_init_End; // call memset to set all malloc'ed memory to 0 + } +} + +static const DWORD partyMemberGetAIOptions_End = 0x49423A; +static void __declspec(naked) NPCStage6Fix2() { + __asm { + imul edx, 204; // multiply record size 204 bytes by NPC number as listed in party.txt + mov eax, dword ptr ds:[FO_VAR_partyMemberAIOptions]; // get starting offset of internal NPC table + jmp partyMemberGetAIOptions_End; // eax + edx = offset of specific NPC record + } +} + // Haenlomal: Check path to critter for attack static void __declspec(naked) MultiHexFix() { __asm { @@ -830,24 +928,6 @@ end: } } -static const DWORD ai_move_steps_closer_run_object_ret = 0x42A169; -static void __declspec(naked) MultiHexCombatRunFix() { - __asm { - test [edi + flags + 1], 0x08; // is target multihex? - jnz multiHex; - test [esi + flags + 1], 0x08; // is source multihex? - jz runTile; -multiHex: - mov edx, [esp + 4]; // source's destination tilenum - cmp [edi + tile], edx; // target's tilenum - jnz runTile; - add esp, 4; - jmp ai_move_steps_closer_run_object_ret; -runTile: - retn; - } -} - static const DWORD ai_move_steps_closer_move_object_ret = 0x42A192; static void __declspec(naked) MultiHexCombatMoveFix() { __asm { @@ -866,6 +946,24 @@ moveTile: } } +static const DWORD ai_move_steps_closer_run_object_ret = 0x42A169; +static void __declspec(naked) MultiHexCombatRunFix() { + __asm { + test [edi + flags + 1], 0x08; // is target multihex? + jnz multiHex; + test [esi + flags + 1], 0x08; // is source multihex? + jz runTile; +multiHex: + mov edx, [esp + 4]; // source's destination tilenum + cmp [edi + tile], edx; // target's tilenum + jnz runTile; + add esp, 4; + jmp ai_move_steps_closer_run_object_ret; +runTile: + retn; + } +} + //checks if an attacked object is a critter before attempting dodge animation static void __declspec(naked) action_melee_hack() { __asm { @@ -1288,30 +1386,28 @@ end: } } -static int __stdcall ItemCountFixStdcall(fo::GameObject* who, fo::GameObject* item) { +static int __stdcall ItemCountFix(fo::GameObject* who, fo::GameObject* item) { int count = 0; for (int i = 0; i < who->invenSize; i++) { auto tableItem = &who->invenTable[i]; if (tableItem->object == item) { count += tableItem->count; } else if (fo::func::item_get_type(tableItem->object) == fo::item_type_container) { - count += ItemCountFixStdcall(tableItem->object, item); + count += ItemCountFix(tableItem->object, item); } } return count; } -static void __declspec(naked) ItemCountFix() { +static void __declspec(naked) item_count_hack() { __asm { - push ebx; push ecx; push edx; // save state push edx; // item push eax; // container-object - call ItemCountFixStdcall; - pop edx; - pop ecx; - pop ebx; // restore + call ItemCountFix; + pop edx; + pop ecx; // restore retn; } } @@ -1624,16 +1720,16 @@ end: static void __declspec(naked) op_obj_can_hear_obj_hack() { __asm { - mov eax, [esp + 0x28 - 0x28 + 4]; // target - mov edx, [esp + 0x28 - 0x24 + 4]; // source + mov eax, [esp + 0x28 - 0x28 + 4]; // target + mov edx, [esp + 0x28 - 0x24 + 4]; // source retn; } } static void __declspec(naked) ai_best_weapon_hook() { __asm { - mov eax, [esp + 0xF4 - 0x10 + 4]; // prev.item - jmp fo::funcoffs::item_w_perk_; + mov eax, [esp + 0xF4 - 0x10 + 4]; // prev.item + jmp fo::funcoffs::item_w_perk_; } } @@ -1654,11 +1750,11 @@ static void __declspec(naked) wmSetupRandomEncounter_hook() { static void __declspec(naked) inven_obj_examine_func_hack() { __asm { mov edx, dword ptr ds:[0x519064]; // inven_display_msg_line - cmp edx, 2; // 2 or more lines + cmp edx, 2; // >2 ja fix; retn; fix: - cmp edx, 9; // 8 lines (half of the display window) + cmp edx, 5; // 4 lines ja limit; dec edx; sub eax, 3; @@ -1666,7 +1762,7 @@ fix: add eax, 3; retn; limit: - mov eax, 57; + mov eax, 30; retn; } } @@ -1870,6 +1966,14 @@ skip: } } +static void __declspec(naked) op_attack_hook() { + __asm { + mov esi, dword ptr [esp + 0x3C + 4]; // free_move + mov ebx, dword ptr [esp + 0x40 + 4]; // add amount damage to target + jmp fo::funcoffs::gdialogActive_; + } +} + static void __declspec(naked) combat_attack_hack() { __asm { mov ebx, ds:[FO_VAR_main_ctd + 0x2C]; // amountTarget @@ -1883,14 +1987,6 @@ end: } } -static void __declspec(naked) op_attack_hook() { - __asm { - mov esi, dword ptr [esp + 0x3C + 4]; // free_move - mov ebx, dword ptr [esp + 0x40 + 4]; // add amount damage to target - jmp fo::funcoffs::gdialogActive_; - } -} - static void __declspec(naked) op_use_obj_on_obj_hack() { __asm { test eax, eax; // source @@ -1942,8 +2038,8 @@ isLoad: static void __declspec(naked) JesseContainerFid() { __asm { - dec edx; // set fid to -1 - jmp fo::funcoffs::obj_new_; + dec edx; // set fid to -1 + jmp fo::funcoffs::obj_new_; } } @@ -2031,11 +2127,11 @@ static void __declspec(naked) obj_load_dude_hook1() { static void __declspec(naked) PrintAMList_hook() { __asm { - cmp ebp, 20; // max line count - jle skip; - mov ebp, 20; + cmp ebp, 20; // max line count + jle skip; + mov ebp, 20; skip: - jmp fo::funcoffs::qsort_; + jmp fo::funcoffs::qsort_; } } @@ -2268,14 +2364,14 @@ skip: static long __fastcall GetFreeTilePlacement(long elev, long tile) { long count = 0, dist = 1; - long freeTile = tile; + long checkTile = tile; long rotation = fo::var::rotation; - while (fo::func::obj_blocking_at(0, freeTile, elev)) { - freeTile = fo::func::tile_num_in_direction(freeTile, rotation, dist); + while (fo::func::obj_blocking_at(0, checkTile, elev)) { + checkTile = fo::func::tile_num_in_direction(checkTile, rotation, dist); if (++count > 5 && ++dist > 5) return tile; if (++rotation > 5) rotation = 0; } - return freeTile; + return checkTile; // free tile } static void __declspec(naked) map_check_state_hook() { @@ -2291,19 +2387,94 @@ static void __declspec(naked) map_check_state_hook() { } } +static void __declspec(naked) op_critter_rm_trait_hook() { + __asm { + mov ebx, [esp + 0x34 - 0x34 + 4]; // amount + test ebx, ebx; + jz skip; + call fo::funcoffs::perk_level_; + test eax, eax; + jz end; + dec dword ptr [esp + 0x34 - 0x34 + 4]; + test ebx, ebx; + jnz end; // continue if amount != 0 +skip: + xor eax, eax; // exit loop +end: + retn; + } +} + +static void __declspec(naked) op_critter_add_trait_hook() { + __asm { + mov edi, edx; // perk id + mov ebp, eax; // source + mov ebx, [esp + 0x34 - 0x34 + 4]; // amount +addLoop: + call fo::funcoffs::perk_add_force_; + test eax, eax; + jnz end; // can't add + mov edx, edi; + mov eax, ebp; + dec ebx; + jnz addLoop; + xor eax, eax; +end: + retn; + } +} + +static const DWORD CorpseShotBlockFix_continue_loop[] = {0x48B99B, 0x48BA0B}; +static void __declspec(naked) obj_shoot_blocking_at_hack0() { + __asm { + mov edx, eax; + mov eax, [eax]; + call fo::funcoffs::critter_is_dead_; // found some object, check if it's a dead critter + test eax, eax; + jz endLoop; // if not, allow breaking the loop (will return this object) + mov eax, edx; + jmp CorpseShotBlockFix_continue_loop[0]; // otherwise continue searching +endLoop: + mov eax, [edx]; + pop ebp; + pop edi; + pop esi; + pop ecx; + retn; + } +} + +// same logic as above, for different loop +static void __declspec(naked) obj_shoot_blocking_at_hack1() { + __asm { + mov eax, [edx]; + call fo::funcoffs::critter_is_dead_; + test eax, eax; + jz endLoop; + jmp CorpseShotBlockFix_continue_loop[1]; +endLoop: + mov eax, [edx]; + pop ebp; + pop edi; + pop esi; + pop ecx; + retn; + } +} + void BugFixes::init() { #ifndef NDEBUG LoadGameHook::OnBeforeGameClose() += PrintAddrList; - if (isDebug && (GetPrivateProfileIntA("Debugging", "BugFixes", 1, ::sfall::ddrawIni) == 0)) return; + if (isDebug && (iniGetInt("Debugging", "BugFixes", 1, ::sfall::ddrawIni) == 0)) return; #endif // Missing game initialization - LoadGameHook::OnGameInit() += MusicVolInitialization; + LoadGameHook::OnBeforeGameInit() += MusicVolInitialization; - // fix vanilla negate operator on float values + // Fix vanilla negate operator on float values MakeCall(0x46AB68, NegateFixHack); - // fix incorrect int-to-float conversion + // Fix incorrect int-to-float conversion // op_mult: SafeWrite16(0x46A3F4, 0x04DB); // replace operator to "fild 32bit" SafeWrite16(0x46A3A8, 0x04DB); @@ -2375,6 +2546,12 @@ void BugFixes::init() dlog("Applying fix for armor reducing NPC original stats when removed.", DL_INIT); HookCall(0x495F3B, partyMemberCopyLevelInfo_hook_stat_level); HookCall(0x45419B, correctFidForRemovedItem_hook_adjust_ac); + // Fix for move_obj_inven_to_obj function + HookCall(0x45C49A, op_move_obj_inven_to_obj_hook); + SafeWrite16(0x45C496, 0x9090); + SafeWrite8(0x45C4A3, 0x75); // jmp > jnz + // Fix for drop_obj function + HookCall(0x49B965, obj_drop_hook); dlogr(" Done", DL_INIT); //} @@ -2440,7 +2617,7 @@ void BugFixes::init() //if (GetConfigInt("Misc", "NPCLevelFix", 1)) { dlog("Applying NPC level fix.", DL_INIT); - HookCall(0x495BC9, (void*)0x495E51); + HookCall(0x495BC9, (void*)0x495E51); // jz 0x495E7F > jz 0x495E51 dlogr(" Done", DL_INIT); //} @@ -2490,13 +2667,24 @@ void BugFixes::init() // Fix for op_lookup_string_proc_ engine function not searching the last procedure in a script SafeWrite8(0x46C7AC, 0x76); // jb > jbe + // Update the AC counter //if (GetConfigInt("Misc", "WieldObjCritterFix", 1)) { dlog("Applying wield_obj_critter fix.", DL_INIT); - SafeWrite8(0x456912, 0x1E); + SafeWrite8(0x456912, 0x1E); // jnz 0x456931 HookCall(0x45697F, op_wield_obj_critter_adjust_ac_hook); dlogr(" Done", DL_INIT); //} + // Enable party members with level 6 protos to reach level 6 + //if (GetConfigInt("Misc", "NPCStage6Fix", 1)) { + dlog("Applying NPC Stage 6 Fix.", DL_INIT); + MakeJump(0x493CE9, NPCStage6Fix1); // partyMember_init_ + MakeJump(0x494224, NPCStage6Fix2); // partyMemberGetAIOptions_ + SafeWrite8(0x494063, 6); // loop should look for a potential 6th stage (partyMember_init_) + SafeWrite8(0x4940BB, 204); // move pointer by 204 bytes instead of 200 + dlogr(" Done", DL_INIT); + //} + //if (GetConfigInt("Misc", "MultiHexPathingFix", 1)) { dlog("Applying MultiHex Pathing Fix.", DL_INIT); MakeCalls(MultiHexFix, {0x42901F, 0x429170}); @@ -2602,7 +2790,7 @@ void BugFixes::init() MakeCall(0x471A94, use_inventory_on_hack); // Fix item_count function returning incorrect value when there is a container-item inside - MakeJump(0x47808C, ItemCountFix); // replacing item_count_ function + MakeJump(0x47808C, item_count_hack); // replacing item_count_ function // Fix for Sequence stat value not being printed correctly when using "print to file" option MakeCall(0x4396F5, Save_as_ASCII_hack, 2); @@ -2635,7 +2823,7 @@ void BugFixes::init() // Fix for player's base EMP DR not being properly initialized when creating a new character and then starting the game HookCall(0x4A22DF, ResetPlayer_hook); - // Fix for add_mult_objs_to_inven only adding 500 of an object when the value of "count" argument is over 99999 + // Fix for add_mult_objs_to_inven only adding 500 of an object when the value of the "count" argument is over 99999 SafeWrite32(0x45A2A0, 0x1869F); // 99999 // Fix for being at incorrect hex after map change when the exit hex in source map is at the same position as @@ -2693,11 +2881,14 @@ void BugFixes::init() } // Fix for AI not checking weapon perks properly when searching for the best weapon - if (GetConfigInt("Misc", "AIBestWeaponFix", 0)) { + int bestWeaponPerkFix = GetConfigInt("Misc", "AIBestWeaponFix", 0); + if (bestWeaponPerkFix > 0) { dlog("Applying AI best weapon choice fix.", DL_INIT); HookCall(0x42954B, ai_best_weapon_hook); - // also change the modifier for having weapon perk to 3x (was 5x) - SafeWriteBatch(0x55, {0x42955E, 0x4296E7}); + // also change the priority multiplier for having weapon perk to 3x (the original is 5x) + if (bestWeaponPerkFix > 1) { + SafeWriteBatch(0x55, {0x42955E, 0x4296E7}); + } dlogr(" Done", DL_INIT); } @@ -2763,14 +2954,6 @@ void BugFixes::init() // Fix returned result value when the file is missing HookCall(0x4C6162, db_freadInt_hook); - // Fix for attack_complex still causing minimum damage to the target when the attacker misses - MakeCall(0x422FE5, combat_attack_hack, 1); - - // Fix for critter_mod_skill taking a negative amount value as a positive - dlog("Applying critter_mod_skill fix.", DL_INIT); - SafeWrite8(0x45B910, 0x7E); // jbe > jle - dlogr(" Done", DL_INIT); - // Fix and repurpose the unused called_shot/num_attack arguments of attack_complex function // also change the behavior of the result flags arguments // called_shot - additional damage, when the damage received by the target is above the specified minimum @@ -2785,6 +2968,14 @@ void BugFixes::init() dlogr(" Done", DL_INIT); } + // Fix for attack_complex still causing minimum damage to the target when the attacker misses + MakeCall(0x422FE5, combat_attack_hack, 1); + + // Fix for critter_mod_skill taking a negative amount value as a positive + dlog("Applying critter_mod_skill fix.", DL_INIT); + SafeWrite8(0x45B910, 0x7E); // jbe > jle + dlogr(" Done", DL_INIT); + // Fix crash when calling use_obj/use_obj_on_obj without using set_self in global scripts // also change the behavior of use_obj_on_obj function // if the object uses the item on itself, then another function is called (not a bug fix) @@ -2895,6 +3086,22 @@ void BugFixes::init() // Remove duplicate code from intface_redraw_ engine function BlockCall(0x45EBBF); + + // Fix for critter_add/rm_trait functions ignoring the value of the "amount" argument + // Note: pass negative amount values to critter_rm_trait to remove all ranks of the perk (vanilla behavior) + HookCall(0x458CDB, op_critter_rm_trait_hook); + HookCall(0x458B3D, op_critter_add_trait_hook); + + // Fix to prevent corpses from blocking line of fire + //if (GetConfigInt("Misc", "CorpseLineOfFireFix", 1)) { + dlog("Applying fix for corpses blocking line of fire.", DL_INIT); + MakeJump(0x48B994, obj_shoot_blocking_at_hack0); + MakeJump(0x48BA04, obj_shoot_blocking_at_hack1); + dlogr(" Done", DL_INIT); + //} + + // Fix for party member's equipped weapon being placed in the incorrect item slot after leveling up + MakeCall(0x495FD9, partyMemberCopyLevelInfo_hack, 1); } } diff --git a/sfall/Modules/Criticals.cpp b/sfall/Modules/Criticals.cpp index a11163dc..434e0b29 100644 --- a/sfall/Modules/Criticals.cpp +++ b/sfall/Modules/Criticals.cpp @@ -76,9 +76,10 @@ void Criticals::ResetCriticalTable(DWORD critter, DWORD bodypart, DWORD slot, DW critTable[critter][bodypart][slot].values[element] = baseCritTable[critter][bodypart][slot].values[element]; } -static void CritTableLoad() { +static int CritTableLoad() { if (mode == 1) { dlogr("Setting up critical hit table using CriticalOverrides.ini (old fmt)", DL_CRITICALS); + if (GetFileAttributes(critTableFile.c_str()) == INVALID_FILE_ATTRIBUTES) return 1; char section[16]; for (DWORD critter = 0; critter < 20; critter++) { for (DWORD part = 0; part < 9; part++) { @@ -88,7 +89,7 @@ static void CritTableLoad() { fo::CritInfo& newEffect = baseCritTable[newCritter][part][crit]; fo::CritInfo& defaultEffect = fo::var::crit_succ_eff[critter][part][crit]; for (int i = 0; i < 7; i++) { - newEffect.values[i] = GetPrivateProfileIntA(section, critNames[i], defaultEffect.values[i], critTableFile.c_str()); + newEffect.values[i] = iniGetInt(section, critNames[i], defaultEffect.values[i], critTableFile.c_str()); if (isDebug) { char logmsg[256]; if (newEffect.values[i] != defaultEffect.values[i]) { @@ -108,15 +109,16 @@ static void CritTableLoad() { if (mode == 3) { dlogr(" and CriticalOverrides.ini (new fmt)", DL_CRITICALS); + if (GetFileAttributes(critTableFile.c_str()) == INVALID_FILE_ATTRIBUTES) return 1; char buf[32], buf2[32], buf3[32]; for (int critter = 0; critter < Criticals::critTableCount; critter++) { sprintf_s(buf, "c_%02d", critter); int all; - if (!(all = GetPrivateProfileIntA(buf, "Enabled", 0, critTableFile.c_str()))) continue; + if (!(all = iniGetInt(buf, "Enabled", 0, critTableFile.c_str()))) continue; for (int part = 0; part < 9; part++) { if (all < 2) { sprintf_s(buf2, "Part_%d", part); - if (!GetPrivateProfileIntA(buf, buf2, 0, critTableFile.c_str())) continue; + if (!iniGetInt(buf, buf2, 0, critTableFile.c_str())) continue; } sprintf_s(buf2, "c_%02d_%d", critter, part); @@ -124,7 +126,7 @@ static void CritTableLoad() { fo::CritInfo& effect = baseCritTable[critter][part][crit]; for (int i = 0; i < 7; i++) { sprintf_s(buf3, "e%d_%s", crit, critNames[i]); - effect.values[i] = GetPrivateProfileIntA(buf2, buf3, effect.values[i], critTableFile.c_str()); + effect.values[i] = iniGetInt(buf2, buf3, effect.values[i], critTableFile.c_str()); } } } @@ -133,6 +135,7 @@ static void CritTableLoad() { dlog("\n", DL_CRITICALS); } } + return 0; } #define SetEntry(critter, bodypart, effect, param, value) fo::var::crit_succ_eff[critter][bodypart][effect].values[param] = value; @@ -240,8 +243,11 @@ static void CriticalTableOverride() { SetEntry(18, 7, 5, 5, 7101); } - CritTableLoad(); - dlogr("Completed applying critical hit table.", DL_INIT); + if (CritTableLoad()) { + dlogr("Failed to initialize critical hit table from file.", DL_INIT); + } else { + dlogr("Completed applying critical hit table.", DL_INIT); + } Inited = true; } #undef SetEntry diff --git a/sfall/Modules/CritterStats.cpp b/sfall/Modules/CritterStats.cpp new file mode 100644 index 00000000..7ea223c4 --- /dev/null +++ b/sfall/Modules/CritterStats.cpp @@ -0,0 +1,452 @@ +/* + * sfall + * Copyright (C) 2008-2019 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "..\main.h" +#include "..\FalloutEngine\Fallout2.h" +#include "LoadGameHook.h" +#include "Objects.h" + +#include "CritterStats.h" + +namespace sfall +{ + +static struct { + long id; + long* proto; +} lastGetProtoID; + +struct ProtoMem { + long* proto; + long pid; + long sharedCount; // if the value is 0, then the prototype can be deleted from memory + + //ProtoMem() {} + ProtoMem(long pid) : proto(nullptr), pid(pid), sharedCount(0) {} // for load + ProtoMem(long* defProto, long pid) : pid(pid), sharedCount(1) { + CreateProtoMem(defProto); + } + + void CreateProtoMem(long* srcProto) { + this->proto = reinterpret_cast(new int32_t[104]); // 416 bytes + memcpy(this->proto, srcProto, 416); + } + + ~ProtoMem() { delete[] proto; } +}; + +std::unordered_map protoMem; // key - critter ID, value - pointer to critter prototype for unique ID + +typedef std::unordered_map::iterator itProtoMem; +static void ModifyAllStats(const itProtoMem&); + +struct StatModify { + long objID; + long objPID; // used for additional verification + long stat; // range from base_stat_srength to bonus_dr_poison + long amount; // current value of the stat + long defVal; // value of the stat before the change + long* s_proto; // shared pointer for quick access to the prototype in memory (should not be saved to file) + + StatModify() : s_proto(nullptr) {} // for load + StatModify(fo::GameObject* critter, long stat, long amount, long* defaultProto, long defVal) { + objID = Objects::SetObjectUniqueID(critter); + objPID = critter->protoId; + this->stat = stat; + this->amount = amount; + this->defVal = defVal; + fo::func::dev_printf("[SFALL] Add modify stat value: %d, def=%d, NPC ID: %d\n", amount, defVal, objID); + + itProtoMem mem = protoMem.find(objID); + if (mem == protoMem.end()) { + mem = protoMem.emplace( + std::piecewise_construct, + std::forward_as_tuple(objID), + std::forward_as_tuple(defaultProto, objPID) + ).first; + } else { + if (mem->second.proto == nullptr) { // prototype has not been created + fo::func::dev_printf("[SFALL] No critter prototype, create new...\n"); + mem->second.CreateProtoMem(defaultProto); + ModifyAllStats(mem); + } + mem->second.sharedCount++; + } + s_proto = mem->second.proto; + } + + long* TryReleaseProtoMem() { + itProtoMem mem = protoMem.find(objID); + if (mem != protoMem.end() && mem->second.pid == objPID) { + if (--mem->second.sharedCount <= 0) { // the counter will be reset + protoMem.erase(mem); // removes the prototype from memory + if (objID == lastGetProtoID.id) lastGetProtoID.id = 0; + s_proto = nullptr; + fo::func::dev_printf("[SFALL] Remove prototype for ID: %d\n", objID); + } + return s_proto; + } + return nullptr; + } +}; + +std::vector bonusStatProto; +std::vector baseStatProto; +// for saveable stats +std::vector s_bonusStatProto; +std::vector s_baseStatProto; + +static long isNotPartyMemberPid; + +//////////////////////////////// CRITTERS STATS //////////////////////////////// +static long GetBaseStatValue(long* proto, long stat) { + return proto[OffsetStat::base + stat]; +} + +static void SetBaseStatValue(long* proto, long stat, long amount) { + proto[OffsetStat::base + stat] = amount; +} + +static long GetBonusStatValue(long* proto, long stat) { + return proto[OffsetStat::bonus + stat]; +} + +static void SetBonusStatValue(long* proto, long stat, long amount) { + proto[OffsetStat::bonus + stat] = amount; +} + +static long GetStatValue(long* proto, long offset) { + return proto[offset]; +} + +static void SetStatValue(long* proto, long offset, long amount) { + proto[offset] = amount; +} + +// Applies all stats parameters, loaded from save file to individual NPC prototypes +static void ModifyAllStats(const itProtoMem &mem) { + if (!mem->second.proto && !fo::CritterCopyProto(mem->second.pid, mem->second.proto)) return; + + for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { + if (itBonus->objID == mem->first && itBonus->objPID == mem->second.pid) { + itBonus->s_proto = mem->second.proto; + SetBonusStatValue(itBonus->s_proto, itBonus->stat, itBonus->amount); + mem->second.sharedCount++; + } + } + for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { + if (itBase->objID == mem->first && itBase->objPID == mem->second.pid) { + itBase->s_proto = mem->second.proto; + SetBaseStatValue(itBase->s_proto, itBase->stat, itBase->amount); + mem->second.sharedCount++; + } + } +} + +static std::vector& GetRefVector(bool bonus, bool isSaved) { + if (isSaved) { + return (bonus) ? s_bonusStatProto : s_baseStatProto; + } + return (bonus) ? bonusStatProto : baseStatProto; +} + +static void AddStat(long stat, fo::GameObject* critter, long amount, long* defaultProto, long offset, bool isSaved) { + std::vector &vec = GetRefVector(offset == OffsetStat::bonus, isSaved); + + fo::func::dev_printf("[SFALL] Set bonus:%d stat:%d, to NPC pid: %d, saved:%d\n", (offset == OffsetStat::bonus), stat, (critter->protoId & 0xFFFF), isSaved); + + offset += stat; + for (auto itStat = vec.begin(); itStat != vec.end(); itStat++) { + if (itStat->objID == critter->id && itStat->objPID == critter->protoId && itStat->stat == stat) { + fo::func::dev_printf("[SFALL] Modify stat value old: %d to new: %d, ID: %d\n", itStat->s_proto[offset], amount, itStat->objID); + if (amount == itStat->defVal) { // set value and value in regular prototype are matched + if (itStat->TryReleaseProtoMem()) SetStatValue(itStat->s_proto, offset, amount); + // remove vector element, without re-moving other elements + if (itStat != vec.cend() - 1) *itStat = vec.back(); + vec.pop_back(); + return; + } + itStat->amount = amount; + if (itStat->s_proto == nullptr) itStat->s_proto = CritterStats::GetProto(critter); + SetStatValue(itStat->s_proto, offset, amount); + return; + } + } + if (vec.capacity() == vec.size()) vec.reserve(vec.size() + 20); + vec.emplace_back(critter, stat, amount, defaultProto, defaultProto[offset]); + SetStatValue(vec.back().s_proto, offset, amount); // set value to individuals prototype +} + +static void __fastcall SetStatToProto(long stat, fo::GameObject* critter, long amount, long* protoBase, long offset, bool isSaved) { + if (critter->protoId != fo::PID_Player) { + if (critter->protoId == isNotPartyMemberPid || !IsPartyMember(critter)) { + isNotPartyMemberPid = critter->protoId; + AddStat(stat, critter, amount, protoBase, offset, isSaved); + return; + } + } + SetStatValue(protoBase, offset + stat, amount); // set value to dude or party member prototype +} + +static void UpdateDefValue(std::vector &vec, long stat, long pid, long amount) { + for (auto itStat = vec.begin(); itStat != vec.end(); itStat++) { + if (itStat->objPID == pid && itStat->stat == stat) { + fo::func::dev_printf("[SFALL] Replace stat default value: %d to: %d, NPC ID: %d\n", itStat->defVal, amount, itStat->objID); + itStat->defVal = amount; + } + } +} + +static void SetStatToAllProtos(long offset, long pid, long amount) { + offset /= 4; + long stat; + if (offset < OffsetStat::bonus) { + stat = offset - OffsetStat::base; + UpdateDefValue(baseStatProto, stat, pid, amount); + UpdateDefValue(s_baseStatProto, stat, pid, amount); + } else { + stat = offset - OffsetStat::bonus; + UpdateDefValue(bonusStatProto, stat, pid, amount); + UpdateDefValue(s_bonusStatProto, stat, pid, amount); + } + for (const auto& mem : protoMem) { + if (mem.second.pid == pid && mem.second.proto) { + SetStatValue(mem.second.proto, offset, amount); + } + }; +} + +static void __declspec(naked) stat_set_bonus_hack() { + __asm { + pushadc; + mov eax, [esp + 16]; // buf proto + sub eax, 0x20; + push 1; // saved + push 44; // offset from bonus_stat_srength + push eax; // regular proto + push ebx; // amount + mov edx, esi; // critter + call SetStatToProto; // ecx - stat + popadc; + retn; + } +} + +static void __declspec(naked) stat_set_base_hack() { + __asm { + pushadc; + mov eax, [esp + 16]; // buf proto + sub eax, 0x20; + push 1; // saved + push 9; // offset from base_stat_srength + push eax; // regular proto + push ebx; // amount + mov edx, esi; // critter + call SetStatToProto; // ecx - stat + popadc; + test ecx, ecx; + retn; + } +} + +static void __declspec(naked) stat_get_proto() { + using namespace fo::Fields; + __asm { + push eax; + push ecx; + mov ecx, eax; // critter; + call CritterStats::GetProto; + pop ecx; + test eax, eax; + jz skip; + add esp, 4; + mov [esp + 4], eax; // replace proto + add [esp], 5; // skip call proto_ptr_ + retn; +skip: + pop eax; + lea edx, [esp + 4]; // buf + mov eax, [eax + protoId]; + retn; + } +} + +static void __declspec(naked) stat_recalc_derived_hook() { + __asm { + pushadc; + mov ecx, ebx; + call CritterStats::GetProto; + pop ecx; + pop edx; + test eax, eax; + jz skip; + add esp, 4; + mov [esp + 4], eax; // replace proto + retn; +skip: + pop eax; + jmp fo::funcoffs::proto_ptr_; + } +} + +// Returns the NPC prototype located in memory +long* __fastcall CritterStats::GetProto(fo::GameObject* critter) { + if (protoMem.empty() || critter->protoId == fo::PID_Player) return nullptr; + if (lastGetProtoID.id == critter->id) return lastGetProtoID.proto; + + auto itMem = protoMem.find(critter->id); + if (itMem != protoMem.end() && itMem->second.pid == critter->protoId) { + if (!itMem->second.proto) { // there is no prototype in memory, create a new one from the regular prototype + fo::func::dev_printf("[SFALL] Modify all stats to NPC ID: %d\n", critter->id); + ModifyAllStats(itMem); // also create prototype + if (!itMem->second.proto) fo::func::debug_printf("[SFALL] Error get critter prototype pid: %d\n", itMem->second.pid); + } + if (itMem->second.proto) { + lastGetProtoID.id = itMem->first; + lastGetProtoID.proto = itMem->second.proto; + } + return itMem->second.proto; + } + return nullptr; // no prototype in memory with this ID, regular prototype will be used +} + +// set_proto_data - sets stats to regular prototype and all individual NPC prototypes (the same PID) +long CritterStats::SetProtoData(long pid, long offset, long amount) { + long* proto; + long result = fo::func::proto_ptr(pid, (fo::Proto**)&proto); + if (result != -1) { + if (!protoMem.empty() && pid != fo::PID_Player && (pid >> 24) == fo::OBJ_TYPE_CRITTER && (offset >= 0x24 && offset <= 0x138)) { + SetStatToAllProtos(offset, pid, amount); + } + *(long*)((BYTE*)proto + offset) = amount; // set to regular prototype + } + return result; +} + +// get_critter_*_stat - gets stats from an individual's prototype or from a regular prototype +long CritterStats::GetStat(fo::GameObject* critter, long stat, long offset) { + long* proto = CritterStats::GetProto(critter); + if (proto == nullptr) fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto); + return (!proto) ? 0 : GetStatValue(proto, stat + offset); // direct get +} + +// set_critter_*_stat - sets stats to an individual's critter prototype +void CritterStats::SetStat(fo::GameObject* critter, long stat, long amount, long offset) { + long* proto; + if (fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto) != -1) { + SetStatToProto(stat, critter, amount, proto, offset, false); // non-saveable stat + } +} + +void CritterStats::SaveStatData(HANDLE file) { + DWORD sizeWrite, count = s_baseStatProto.size(); + WriteFile(file, &count, 4, &sizeWrite, 0); + for (size_t i = 0; i < count; i++) { + WriteFile(file, &s_baseStatProto[i], 20, &sizeWrite, 0); + } + count = s_bonusStatProto.size(); + WriteFile(file, &count, 4, &sizeWrite, 0); + for (size_t i = 0; i < count; i++) { + WriteFile(file, &s_bonusStatProto[i], 20, &sizeWrite, 0); + } +} + +bool CritterStats::LoadStatData(HANDLE file) { + DWORD count, sizeRead; + ReadFile(file, &count, 4, &sizeRead, 0); + //if (sizeRead != 4) return true; + if (count) s_baseStatProto.reserve(count + 10); + for (size_t i = 0; i < count; i++) { + StatModify data; + ReadFile(file, &data, 20, &sizeRead, 0); + if (sizeRead != 20) return true; + s_baseStatProto.emplace_back(data); + //if (protoMem.find(data.objID) == protoMem.end()) { + protoMem.emplace(data.objID, data.objPID); + //} + } + ReadFile(file, &count, 4, &sizeRead, 0); + //if (sizeRead != 4) return true; + if (count) s_bonusStatProto.reserve(count + 10); + for (size_t i = 0; i < count; i++) { + StatModify data; + ReadFile(file, &data, 20, &sizeRead, 0); + if (sizeRead != 20) return true; + s_bonusStatProto.emplace_back(data); + //if (protoMem.find(data.objID) == protoMem.end()) { + protoMem.emplace(data.objID, data.objPID); + //} + } + return false; +} + +static void ClearAllStats() { + bonusStatProto.clear(); + baseStatProto.clear(); + lastGetProtoID.id = 0; +} + +static void FlushAllProtos() { + for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { + itBonus->s_proto = nullptr; + } + for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { + itBase->s_proto = nullptr; + } + for (auto& mem : protoMem) { + delete[] mem.second.proto; + mem.second.proto = nullptr; + mem.second.sharedCount = 0; + }; + ClearAllStats(); +} + +static void __declspec(naked) map_save_in_game_hook() { + __asm { + call FlushAllProtos; + jmp fo::funcoffs::proto_remove_all_; + } +} + +static void StatsProtoReset() { + ClearAllStats(); + s_bonusStatProto.clear(); + s_baseStatProto.clear(); + protoMem.clear(); + isNotPartyMemberPid = 0; +} + +void CritterStats::RecalcDerivedHook() { + HookCall(0x4AF761, stat_recalc_derived_hook); +} + +void CritterStats::init() { + MakeCall(0x4AF6AD, stat_set_bonus_hack, 1); + MakeCall(0x4AF5B8, stat_set_base_hack); + MakeCalls(stat_get_proto, { + 0x4AF49B, // stat_get_bonus_ + 0x4AF455 // stat_get_base_direct_ + }); + HookCall(0x483E0B, map_save_in_game_hook); // remove all protos + + LoadGameHook::OnGameReset() += StatsProtoReset; +} + +} diff --git a/sfall/Modules/CritterStats.h b/sfall/Modules/CritterStats.h new file mode 100644 index 00000000..b6f1c45d --- /dev/null +++ b/sfall/Modules/CritterStats.h @@ -0,0 +1,47 @@ +/* + * sfall + * Copyright (C) 2008-2019 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Module.h" + +namespace sfall +{ + +enum OffsetStat : long { + base = 9, // offset from base_stat_srength + bonus = 44 // offset from bonus_stat_srength +}; + +class CritterStats : public Module { +public: + const char* name() { return "CritterStats"; } + void init(); + + static long* __fastcall GetProto(fo::GameObject* critter); + static void RecalcDerivedHook(); + + static long GetStat(fo::GameObject* critter, long stat, long offset); + static void SetStat(fo::GameObject* critter, long stat, long amount, long offset); + static long SetProtoData(long pid, long offset, long amount); + + static void SaveStatData(HANDLE file); + static bool LoadStatData(HANDLE file); +}; + +} diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index 02fa3d64..afe5a0dc 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -310,6 +310,23 @@ hide: } } +static char* artDbgMsg = "Error: file not found: %s\n"; +static void __declspec(naked) art_data_size_hook() { + __asm { + test edi, edi; + jz artNotExist; + retn; +artNotExist: + mov eax, [esp + 0x124 - 0x1C + 4]; // filename + push eax; + push artDbgMsg; + call fo::funcoffs::debug_printf_; + add esp, 8; + BREAKPOINT; // break program + retn; + } +} + static void __declspec(naked) win_debug_hook() { __asm { call fo::funcoffs::debug_log_; @@ -319,8 +336,8 @@ static void __declspec(naked) win_debug_hook() { } } -void DebugModePatch() { - DWORD dbgMode = GetPrivateProfileIntA("Debugging", "DebugMode", 0, ::sfall::ddrawIni); +static void DebugModePatch() { + DWORD dbgMode = iniGetInt("Debugging", "DebugMode", 0, ::sfall::ddrawIni); if (dbgMode) { dlog("Applying debugmode patch.", DL_INIT); // If the player is using an exe with the debug patch already applied, just skip this block without erroring @@ -342,15 +359,18 @@ void DebugModePatch() { } else { SafeWrite32(0x4C6D9C, (DWORD)debugGnw); } - if (GetPrivateProfileIntA("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { + if (iniGetInt("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { MakeJump(0x453FD2, dbg_error_hack); } + // prints a debug message about missing art file for critters and interrupts game execution + HookCall(0x419B65, art_data_size_hook); + dlogr(" Done", DL_INIT); } } -void DontDeleteProtosPatch() { - if (GetPrivateProfileIntA("Debugging", "DontDeleteProtos", 0, ::sfall::ddrawIni)) { +static void DontDeleteProtosPatch() { + if (iniGetInt("Debugging", "DontDeleteProtos", 0, ::sfall::ddrawIni)) { dlog("Applying permanent protos patch.", DL_INIT); SafeWrite8(0x48007E, 0xEB); dlogr(" Done", DL_INIT); diff --git a/sfall/Modules/Drugs.cpp b/sfall/Modules/Drugs.cpp index 8813a703..6b697448 100644 --- a/sfall/Modules/Drugs.cpp +++ b/sfall/Modules/Drugs.cpp @@ -268,9 +268,9 @@ void Drugs::init() { dlog("Applying drugs patch...", DL_INIT); const char* iniDrugs = drugsFile.insert(0, ".\\").c_str(); - if (GetPrivateProfileIntA("main", "JetWithdrawal", 0, iniDrugs) == 1) SafeWrite8(0x47A3A8, 0); + if (iniGetInt("main", "JetWithdrawal", 0, iniDrugs) == 1) SafeWrite8(0x47A3A8, 0); - int count = GetPrivateProfileIntA("main", "Count", 0, iniDrugs); + int count = iniGetInt("main", "Count", 0, iniDrugs); if (count > 0) { if (count > drugsMax) count = drugsMax; drugs = new sDrugs[count](); @@ -279,12 +279,12 @@ void Drugs::init() { char section[4]; for (int i = 1; i <= count; i++) { _itoa_s(i, section, 10); - int pid = GetPrivateProfileIntA(section, "PID", 0, iniDrugs); + int pid = iniGetInt(section, "PID", 0, iniDrugs); if (pid > 0) { CheckEngineNumEffects(set, pid); drugs[drugsCount].drugPid = pid; - drugs[drugsCount].addictTimeOff = drugs[drugsCount].iniAddictTimeOff = GetPrivateProfileIntA(section, "AddictTime", 0, iniDrugs); - long ef = GetPrivateProfileIntA(section, "NumEffects", -1, iniDrugs); + drugs[drugsCount].addictTimeOff = drugs[drugsCount].iniAddictTimeOff = iniGetInt(section, "AddictTime", 0, iniDrugs); + long ef = iniGetInt(section, "NumEffects", -1, iniDrugs); if (set != -1) { if (ef < -1) { ef = -1; @@ -298,12 +298,12 @@ void Drugs::init() { drugs[drugsCount].numEffects = ef; // -1 to use the value from the engine } else { drugs[drugsCount].numEffects = drugs[drugsCount].iniNumEffects = max(0, ef); - int gvar = GetPrivateProfileIntA(section, "GvarID", 0, iniDrugs); + int gvar = iniGetInt(section, "GvarID", 0, iniDrugs); drugs[drugsCount].gvarID = max(0, gvar); // not allow negative values if (gvar) { - int msg = GetPrivateProfileIntA(section, "TextID", -1, iniDrugs); + int msg = iniGetInt(section, "TextID", -1, iniDrugs); drugs[drugsCount].msgID = (msg > 0) ? msg : -1; - drugs[drugsCount].frmID = GetPrivateProfileIntA(section, "FrmID", -1, iniDrugs); + drugs[drugsCount].frmID = iniGetInt(section, "FrmID", -1, iniDrugs); addictionGvarCount++; } } diff --git a/sfall/Modules/Elevators.cpp b/sfall/Modules/Elevators.cpp index 4d5b7f7d..7c210234 100644 --- a/sfall/Modules/Elevators.cpp +++ b/sfall/Modules/Elevators.cpp @@ -28,8 +28,6 @@ static const int exitsPerElevator = 4; static const int vanillaElevatorCount = 24; static const int elevatorCount = 50; -//static char elevFile[MAX_PATH] = ".\\"; - static DWORD elevatorType[elevatorCount] = {0}; static fo::ElevatorExit elevatorExits[elevatorCount][exitsPerElevator] = {0}; // _retvals static fo::ElevatorFrms elevatorsFrms[elevatorCount] = {0}; // _intotal @@ -115,26 +113,26 @@ static void LoadElevators(const char* elevFile) { for (int i = 0; i < vanillaElevatorCount; i++) elevatorType[i] = i; char section[4]; - if (elevFile) { + if (elevFile && GetFileAttributes(elevFile) != INVALID_FILE_ATTRIBUTES) { for (int i = 0; i < elevatorCount; i++) { _itoa_s(i, section, 10); - int type = GetPrivateProfileIntA(section, "Image", elevatorType[i], elevFile); + int type = iniGetInt(section, "Image", elevatorType[i], elevFile); elevatorType[i] = min(type, elevatorCount - 1); if (i >= vanillaElevatorCount) { - int cBtn = GetPrivateProfileIntA(section, "ButtonCount", 2, elevFile); + int cBtn = iniGetInt(section, "ButtonCount", 2, elevFile); if (cBtn > exitsPerElevator) cBtn = exitsPerElevator; elevatorsBtnCount[i] = max(2, cBtn); } - elevatorsFrms[i].main = GetPrivateProfileIntA(section, "MainFrm", elevatorsFrms[i].main, elevFile); - elevatorsFrms[i].buttons = GetPrivateProfileIntA(section, "ButtonsFrm", elevatorsFrms[i].buttons, elevFile); + elevatorsFrms[i].main = iniGetInt(section, "MainFrm", elevatorsFrms[i].main, elevFile); + elevatorsFrms[i].buttons = iniGetInt(section, "ButtonsFrm", elevatorsFrms[i].buttons, elevFile); char setting[32]; for (int j = 0; j < exitsPerElevator; j++) { sprintf_s(setting, "ID%d", j + 1); - elevatorExits[i][j].id = GetPrivateProfileIntA(section, setting, elevatorExits[i][j].id, elevFile); + elevatorExits[i][j].id = iniGetInt(section, setting, elevatorExits[i][j].id, elevFile); sprintf_s(setting, "Elevation%d", j + 1); - elevatorExits[i][j].elevation = GetPrivateProfileIntA(section, setting, elevatorExits[i][j].elevation, elevFile); + elevatorExits[i][j].elevation = iniGetInt(section, setting, elevatorExits[i][j].elevation, elevFile); sprintf_s(setting, "Tile%d", j + 1); - elevatorExits[i][j].tile = GetPrivateProfileIntA(section, setting, elevatorExits[i][j].tile, elevFile); + elevatorExits[i][j].tile = iniGetInt(section, setting, elevatorExits[i][j].tile, elevFile); } } } diff --git a/sfall/Modules/Explosions.cpp b/sfall/Modules/Explosions.cpp index 359874f3..eac6d6fb 100644 --- a/sfall/Modules/Explosions.cpp +++ b/sfall/Modules/Explosions.cpp @@ -86,7 +86,7 @@ next: test al, al; jz skiplight; mov eax, [esp + 40]; // projectile ptr - 1st arg - mov edx, 0xFFFF0008; // maximum radius + intensity (see anim_set_check__light_fix) + mov edx, 0xFFFF0008; // maximum radius + intensity (see anim_set_check_light_fix) xor ebx, ebx; call fo::funcoffs::register_object_light_; skiplight: @@ -105,7 +105,7 @@ static void __declspec(naked) explosion_lighting_fix2() { call fo::funcoffs::register_object_funset_; mov eax, [esp + 24]; // explosion obj ptr - mov edx, 0xFFFF0008; // maximum radius + intensity (see anim_set_check__light_fix) + mov edx, 0xFFFF0008; // maximum radius + intensity (see anim_set_check_light_fix) xor ebx, ebx; call fo::funcoffs::register_object_light_; @@ -117,13 +117,13 @@ static void __declspec(naked) explosion_lighting_fix2() { } } -DWORD _stdcall LogThis(DWORD value1, DWORD value2, DWORD value3) { - dlog_f("anim_set_check__light_fix: object 0x%X, something 0x%X, radius 0x%X", DL_MAIN, value1, value2, value3); - return value1; -} +//DWORD _stdcall LogThis(DWORD value1, DWORD value2, DWORD value3) { +// dlog_f("anim_set_check_light_fix: object 0x%X, something 0x%X, radius 0x%X", DL_MAIN, value1, value2, value3); +// return value1; +//} -static const DWORD anim_set_check__light_back = 0x415A4C; -static void __declspec(naked) anim_set_check__light_fix() { +static const DWORD anim_set_check_light_back = 0x415A4C; +static void __declspec(naked) anim_set_check_light_fix() { __asm { mov eax, [esi + 4]; // object lea ecx, [esp + 16]; // unknown.. something related to next "tile_refresh_rect" call? @@ -139,7 +139,7 @@ static void __declspec(naked) anim_set_check__light_fix() { nothingspecial: mov ebx, [eax + 112]; // object current light intensity (original behavior) end: - jmp anim_set_check__light_back; // jump back right to the "obj_set_light" call + jmp anim_set_check_light_back; // jump back right to the "obj_set_light" call } } @@ -150,7 +150,7 @@ static void __declspec(naked) fire_dance_lighting_fix1() { push edx; push ebx; mov eax, esi; // projectile ptr - 1st arg - mov edx, 0xFFFF0002; // maximum radius + intensity (see anim_set_check__light_fix) + mov edx, 0xFFFF0002; // maximum radius + intensity (see anim_set_check_light_fix) xor ebx, ebx; call fo::funcoffs::register_object_light_; mov eax, esi; @@ -158,7 +158,7 @@ static void __declspec(naked) fire_dance_lighting_fix1() { pop edx; call fo::funcoffs::register_object_animate_; // overwritten call mov eax, esi; // projectile ptr - 1st arg - mov edx, 0x00010000; // maximum radius + intensity (see anim_set_check__light_fix) + mov edx, 0x00010000; // maximum radius + intensity (see anim_set_check_light_fix) mov ebx, -1; call fo::funcoffs::register_object_light_; jmp fire_dance_lighting_back; // jump back @@ -343,15 +343,15 @@ static const size_t numArtChecks = sizeof(explosion_art_adr) / sizeof(explosion_ static const size_t numDmgChecks = sizeof(explosion_dmg_check_adr) / sizeof(explosion_dmg_check_adr[0]); enum MetaruleExplosionsMode { - EXPL_FORCE_EXPLOSION_PATTERN = 1, - EXPL_FORCE_EXPLOSION_ART = 2, - EXPL_FORCE_EXPLOSION_RADIUS = 3, - EXPL_FORCE_EXPLOSION_DMGTYPE = 4, - EXPL_STATIC_EXPLOSION_RADIUS = 5, - EXPL_GET_EXPLOSION_DAMAGE = 6, + EXPL_FORCE_EXPLOSION_PATTERN = 1, + EXPL_FORCE_EXPLOSION_ART = 2, + EXPL_FORCE_EXPLOSION_RADIUS = 3, + EXPL_FORCE_EXPLOSION_DMGTYPE = 4, + EXPL_STATIC_EXPLOSION_RADIUS = 5, + EXPL_GET_EXPLOSION_DAMAGE = 6, EXPL_SET_DYNAMITE_EXPLOSION_DAMAGE = 7, - EXPL_SET_PLASTIC_EXPLOSION_DAMAGE = 8, - EXPL_SET_EXPLOSION_MAX_TARGET = 9, + EXPL_SET_PLASTIC_EXPLOSION_DAMAGE = 8, + EXPL_SET_EXPLOSION_MAX_TARGET = 9, }; static void SetExplosionRadius(int arg1, int arg2) { @@ -471,18 +471,17 @@ void ResetExplosionSettings() { } void ResetExplosionRadius() { - if (set_expl_radius_grenade != 2 || set_expl_radius_rocket != 3) - SetExplosionRadius(2, 3); + if (set_expl_radius_grenade != 2 || set_expl_radius_rocket != 3) SetExplosionRadius(2, 3); } static void ResetExplosionDamage() { if (!explosives.empty()) explosives.clear(); if (!explosionsDamageReset) return; - SafeWrite32(dynamite_max_dmg_addr, dynamite_maxDmg); SafeWrite32(dynamite_min_dmg_addr, dynamite_minDmg); - SafeWrite32(plastic_max_dmg_addr, plastic_maxDmg); + SafeWrite32(dynamite_max_dmg_addr, dynamite_maxDmg); SafeWrite32(plastic_min_dmg_addr, plastic_minDmg); + SafeWrite32(plastic_max_dmg_addr, plastic_maxDmg); explosionsDamageReset = false; } @@ -494,7 +493,7 @@ void Explosions::init() { dlog("Applying Explosion changes.", DL_INIT); MakeJump(0x4118E1, ranged_attack_lighting_fix); MakeJump(0x410A4A, fire_dance_lighting_fix1); - MakeJump(0x415A3F, anim_set_check__light_fix); // this allows to change light intensity + MakeJump(0x415A3F, anim_set_check_light_fix); // this allows to change light intensity dlogr(" Done", DL_INIT); } diff --git a/sfall/Modules/ExtraSaveSlots.cpp b/sfall/Modules/ExtraSaveSlots.cpp index 068738fa..a697bf97 100644 --- a/sfall/Modules/ExtraSaveSlots.cpp +++ b/sfall/Modules/ExtraSaveSlots.cpp @@ -62,11 +62,11 @@ void LoadPageOffsets() { sprintf_s(LoadPath, MAX_PATH, filename, fo::var::patches); - fo::var::slot_cursor = GetPrivateProfileInt("POSITION", "ListNum", 0, LoadPath); + fo::var::slot_cursor = iniGetInt("POSITION", "ListNum", 0, LoadPath); if (fo::var::slot_cursor > 9) { fo::var::slot_cursor = 0; } - LSPageOffset = GetPrivateProfileInt("POSITION", "PageOffset", 0, LoadPath); + LSPageOffset = iniGetInt("POSITION", "PageOffset", 0, LoadPath); if (LSPageOffset > 9990) { LSPageOffset = 0; } @@ -475,7 +475,7 @@ static void __declspec(naked) SaveGame_hack1() { void ExtraSaveSlots::init() { bool extraSaveSlots = (GetConfigInt("Misc", "ExtraSaveSlots", 0) != 0); if (extraSaveSlots) { - dlog("Running EnableSuperSaving()", DL_INIT); + dlog("Applying extra save slots patch.", DL_INIT); EnableSuperSaving(); dlogr(" Done", DL_INIT); } diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index 339ea3c1..49ed7a78 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -997,7 +997,8 @@ void Graphics::init() { #define _DLL_NAME "d3dx9_43.dll" HMODULE h = LoadLibraryEx(_DLL_NAME, 0, LOAD_LIBRARY_AS_DATAFILE); if (!h) { - MessageBoxA(0, "You have selected graphics mode 4 or 5, but " _DLL_NAME " is missing\nSwitch back to mode 0, or install an up to date version of DirectX", "Error", 0); + MessageBoxA(0, "You have selected graphics mode 4 or 5, but " _DLL_NAME " is missing.\n" + "Switch back to mode 0, or install an up to date version of DirectX.", "Error", MB_TASKMODAL | MB_ICONERROR); ExitProcess(-1); } else { FreeLibrary(h); diff --git a/sfall/Modules/HeroAppearance.cpp b/sfall/Modules/HeroAppearance.cpp index 93e176d8..70fd03bc 100644 --- a/sfall/Modules/HeroAppearance.cpp +++ b/sfall/Modules/HeroAppearance.cpp @@ -45,12 +45,14 @@ BYTE *charScrnBackSurface = nullptr; DWORD charRotTick = 0; DWORD charRotOri = 0; +bool raceButtons = false, styleButtons = false; int currentRaceVal = 0, currentStyleVal = 0; // holds Appearance values to restore after global reset in NewGame2 function in LoadGameHooks.cpp DWORD critterListSize = 0, critterArraySize = 0; // Critter art list size -fo::PathNode **tempPathPtr = &fo::var::paths; -fo::PathNode *heroPathPtr = nullptr; -fo::PathNode *racePathPtr = nullptr; +fo::PathNode **heroAppPaths = &fo::var::paths; +// index: 0 - only folder (w/o extension .dat), 1 - file or folder .dat +fo::PathNode *heroPathPtr[2] = {nullptr, nullptr}; +fo::PathNode *racePathPtr[2] = {nullptr, nullptr}; // for word wrapping typedef struct LineNode { @@ -184,47 +186,64 @@ static void GetAppearanceGlobals(int *race, int *style) { static __declspec(noinline) int _stdcall LoadHeroDat(unsigned int race, unsigned int style, bool flush = false) { if (flush) fo::func::art_flush(); - if (heroPathPtr->pDat) { // unload previous Dats - UnloadDat(heroPathPtr->pDat); - heroPathPtr->pDat = nullptr; - heroPathPtr->isDat = 0; + if (heroPathPtr[1]->pDat) { // unload previous Dats + UnloadDat(heroPathPtr[1]->pDat); + heroPathPtr[1]->pDat = nullptr; + heroPathPtr[1]->isDat = 0; } - if (racePathPtr->pDat) { - UnloadDat(racePathPtr->pDat); - racePathPtr->pDat = nullptr; - racePathPtr->isDat = 0; + if (racePathPtr[1]->pDat) { + UnloadDat(racePathPtr[1]->pDat); + racePathPtr[1]->pDat = nullptr; + racePathPtr[1]->isDat = 0; } const char sex = GetSex(); - - sprintf_s(heroPathPtr->path, 64, appearancePathFmt, sex, race, style, ".dat"); - int result = GetFileAttributes(heroPathPtr->path); - if (result != INVALID_FILE_ATTRIBUTES && !(result & FILE_ATTRIBUTE_DIRECTORY)) { // check if Dat exists for selected appearance - heroPathPtr->pDat = LoadDat(heroPathPtr->path); - heroPathPtr->isDat = 1; - } else { - sprintf_s(heroPathPtr->path, 64, appearancePathFmt, sex, race, style, ""); - if (GetFileAttributes(heroPathPtr->path) == INVALID_FILE_ATTRIBUTES) // check if folder exists for selected appearance - return -1; + bool folderIsExist = false, heroDatIsExist = false; + // check if folder exists for selected appearance + sprintf_s(heroPathPtr[0]->path, 64, appearancePathFmt, sex, race, style, ""); + if (GetFileAttributes(heroPathPtr[0]->path) != INVALID_FILE_ATTRIBUTES) { + folderIsExist = true; + } + // check if Dat exists for selected appearance + sprintf_s(heroPathPtr[1]->path, 64, appearancePathFmt, sex, race, style, ".dat"); + int result = GetFileAttributes(heroPathPtr[1]->path); + if (result != INVALID_FILE_ATTRIBUTES) { + if (!(result & FILE_ATTRIBUTE_DIRECTORY)) { + heroPathPtr[1]->pDat = LoadDat(heroPathPtr[1]->path); + heroPathPtr[1]->isDat = 1; + } + if (folderIsExist) heroPathPtr[0]->next = heroPathPtr[1]; + heroDatIsExist = true; + } else if (!folderIsExist) { + return -1; // no .dat files and folder } - tempPathPtr = &heroPathPtr; // set path for selected appearance - heroPathPtr->next = &fo::var::paths[0]; + heroAppPaths = &heroPathPtr[1 - folderIsExist]; // set path for selected appearance + heroPathPtr[0 + heroDatIsExist]->next = &fo::var::paths[0]; // heroPathPtr[] >> foPaths if (style != 0) { - sprintf_s(racePathPtr->path, 64, appearancePathFmt, sex, race, 0, ".dat"); - int result = GetFileAttributes(racePathPtr->path); - if (result != INVALID_FILE_ATTRIBUTES && !(result & FILE_ATTRIBUTE_DIRECTORY)) { // check if Dat exists for selected race base appearance - racePathPtr->pDat = LoadDat(racePathPtr->path); - racePathPtr->isDat = 1; - } else { - sprintf_s(racePathPtr->path, 64, appearancePathFmt, sex, race, 0, ""); + bool raceDatIsExist = false, folderIsExist = false; + // check if folder exists for selected race base appearance + sprintf_s(racePathPtr[0]->path, 64, appearancePathFmt, sex, race, 0, ""); + if (GetFileAttributes(racePathPtr[0]->path) != INVALID_FILE_ATTRIBUTES) { + folderIsExist = true; + } + // check if Dat (or folder) exists for selected race base appearance + sprintf_s(racePathPtr[1]->path, 64, appearancePathFmt, sex, race, 0, ".dat"); + int result = GetFileAttributes(racePathPtr[1]->path); + if (result != INVALID_FILE_ATTRIBUTES) { + if (!(result & FILE_ATTRIBUTE_DIRECTORY)) { + racePathPtr[1]->pDat = LoadDat(racePathPtr[1]->path); + racePathPtr[1]->isDat = 1; + } + if (folderIsExist) racePathPtr[0]->next = racePathPtr[1]; + raceDatIsExist = true; + } else if (!folderIsExist) { + return 0; } - if (GetFileAttributes(racePathPtr->path) != INVALID_FILE_ATTRIBUTES) { // check if folder/Dat exists for selected race base appearance - heroPathPtr->next = racePathPtr; // set path for selected race base appearance - racePathPtr->next = &fo::var::paths[0]; - } + heroPathPtr[0 + heroDatIsExist]->next = racePathPtr[1 - folderIsExist]; // set path for selected race base appearance + racePathPtr[0 + raceDatIsExist]->next = &fo::var::paths[0]; // insert racePathPtr in chain path: heroPathPtr[] >> racePathPtr[] >> foPaths } return 0; } @@ -233,12 +252,12 @@ static __declspec(noinline) int _stdcall LoadHeroDat(unsigned int race, unsigned static void __declspec(naked) LoadNewHeroArt() { __asm { cmp byte ptr ds:[esi], 'r'; - je isReading; + jne isNotReading; + mov ecx, heroAppPaths; + mov ecx, dword ptr ds:[ecx]; // set app path + retn; +isNotReading: mov ecx, FO_VAR_paths; - jmp setPath; -isReading: - mov ecx, tempPathPtr; -setPath: mov ecx, dword ptr ds:[ecx]; retn; } @@ -333,15 +352,20 @@ endFunc: static long _stdcall AddHeroCritNames() { // art_init_ auto &critterArt = fo::var::art[fo::OBJ_TYPE_CRITTER]; critterListSize = critterArt.total / 2; + if (critterListSize > 2048) { + MessageBoxA(0, "This mod cannot be used because the maximum limit of the FID count in the critters.lst is exceeded.\n" + "Please disable the mod and restart the game.", "Hero Appearance mod", MB_TASKMODAL | MB_ICONERROR); + ExitProcess(-1); + } critterArraySize = critterListSize * 13; - char *CritList = critterArt.names; // critter list offset - char *HeroList = CritList + critterArraySize; // set start of hero critter list after regular critter list + char *CritList = critterArt.names; // critter list offset + char *HeroList = CritList + critterArraySize; // set start of hero critter list after regular critter list memset(HeroList, 0, critterArraySize); - for (DWORD i = 0; i < critterListSize; i++) { // copy critter name list to hero name list - *HeroList = '_'; // insert a '_' char at the front of new hero critt names. fallout wont load the same name twice + for (DWORD i = 0; i < critterListSize; i++) { // copy critter name list to hero name list + *HeroList = '_'; // insert a '_' char at the front of new hero critt names. fallout wont load the same name twice memcpy(HeroList + 1, CritList, 11); HeroList += 13; CritList += 13; @@ -421,11 +445,11 @@ void _stdcall SetNewCharAppearanceGlobals() { void _stdcall SetHeroStyle(int newStyleVal) { if (!HeroAppearance::appModEnabled || newStyleVal == currentStyleVal) return; - if (LoadHeroDat(currentRaceVal, newStyleVal, true) != 0) { // if new style cannot be set + if (LoadHeroDat(currentRaceVal, newStyleVal, true) != 0) { // if new style cannot be set if (currentRaceVal == 0 && newStyleVal == 0) { - currentStyleVal = 0; // ignore error if appearance = default + currentStyleVal = 0; // ignore error if appearance = default } else { - LoadHeroDat(currentRaceVal, currentStyleVal); // reload original style + LoadHeroDat(currentRaceVal, currentStyleVal); // reload original style } } else { currentStyleVal = newStyleVal; @@ -438,10 +462,10 @@ void _stdcall SetHeroStyle(int newStyleVal) { void _stdcall SetHeroRace(int newRaceVal) { if (!HeroAppearance::appModEnabled || newRaceVal == currentRaceVal) return; - if (LoadHeroDat(newRaceVal, 0, true) != 0) { // if new race fails with style at 0 + if (LoadHeroDat(newRaceVal, 0, true) != 0) { // if new race fails with style at 0 if (newRaceVal == 0) { currentRaceVal = 0; - currentStyleVal = 0; // ignore if appearance = default + currentStyleVal = 0; // ignore if appearance = default } else { LoadHeroDat(currentRaceVal, currentStyleVal); // reload original race & style } @@ -533,7 +557,7 @@ static void DrawPCConsole() { sub_draw(70, 102, 640, 480, 338, 78, charScrnBackSurface, 70, 102, 0, 0, ConSurface, 0); //DWORD critNum = fo::var::art_vault_guy_num; // pointer to current base hero critter FrmId - DWORD critNum = fo::var::obj_dude->artFid; // pointer to current armored hero critter FrmId + DWORD critNum = fo::var::obj_dude->artFid; // pointer to current armored hero critter FrmId DrawBody(critNum, ConSurface); sub_draw(70, 102, 70, 102, 0, 0, ConSurface, WinInfo->width, WinInfo->height, 338, 78, WinInfo->surface, 0); @@ -714,13 +738,13 @@ void _stdcall HeroSelectWindow(int raceStyleFlag) { BYTE *ConDraw = new BYTE [70 * 102]; int button = 0; - bool drawFlag = true; // redraw flag for char note pad + bool drawFlag = true; // redraw flag for char note pad DWORD RotSpeed = *(DWORD*)0x47066B; // get rotation speed - inventory rotation speed DWORD RedrawTick = 0, NewTick = 0, OldTick = 0; - DWORD critNum = fo::var::art_vault_guy_num; // pointer to current base hero critter FrmID - //DWORD critNum = fo::var::obj_dude->artFID; // pointer to current armored hero critter FrmID + DWORD critNum = fo::var::art_vault_guy_num; // pointer to current base hero critter FrmID + //DWORD critNum = fo::var::obj_dude->artFID; // pointer to current armored hero critter FrmID int raceVal = currentRaceVal, styleVal = currentStyleVal; // show default style when setting race if (!isStyle) styleVal = 0; @@ -756,7 +780,7 @@ void _stdcall HeroSelectWindow(int raceStyleFlag) { } button = fo::func::get_input(); - if (button == 0x148) { // previous style/race -up arrow button pushed + if (button == 0x148) { // previous style/race - up arrow button pushed PlayAcm("ib1p1xx1"); if (isStyle) { @@ -1068,7 +1092,7 @@ NoVaultSuit: } } -// Create race and style selection buttons when creating a character +// Create race and style selection buttons when creating a character (hero) static void __declspec(naked) AddCharScrnButtons() { __asm { pushad; // prolog @@ -1080,10 +1104,10 @@ static void __declspec(naked) AddCharScrnButtons() { WinRef = fo::var::edit_win; // char screen window ref // race and style title buttons - fo::func::win_register_button(WinRef, 332, 0, 82, 32, -1, -1, 0x501, -1, 0, 0, 0, 0); + fo::func::win_register_button(WinRef, 332, 0, 82, 32, -1, -1, 0x501, -1, 0, 0, 0, 0); fo::func::win_register_button(WinRef, 332, 226, 82, 32, -1, -1, 0x502, -1, 0, 0, 0, 0); - if (fo::var::glblmode == 1) { // equals 1 if new char screen - equals 0 if ingame char screen + if (fo::var::glblmode == 1 && (styleButtons || raceButtons)) { // equals 1 if new char screen - equals 0 if ingame char screen if (newButtonSurface == nullptr) { newButtonSurface = new BYTE [20 * 18 * 4]; @@ -1108,20 +1132,13 @@ static void __declspec(naked) AddCharScrnButtons() { frmSurface = nullptr; } - - // check if Data exists for other races male or female, and if so enable race selection buttons - if (GetFileAttributes("Appearance\\hmR01S00") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR01S00") != INVALID_FILE_ATTRIBUTES || - GetFileAttributes("Appearance\\hmR01S00.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR01S00.dat") != INVALID_FILE_ATTRIBUTES) { - // race selection buttons + if (raceButtons) { // race selection buttons fo::func::win_register_button(WinRef, 348, 37, 20, 18, -1, -1, -1, 0x511, newButtonSurface, newButtonSurface + (20 * 18), 0, 0x20); - fo::func::win_register_button(WinRef, 373, 37, 20, 18, -1, -1, -1, 0x513, newButtonSurface + (20 * 18 * 2), newButtonSurface + (20 * 18 * 3), 0, 0x20); + fo::func::win_register_button(WinRef, 374, 37, 20, 18, -1, -1, -1, 0x513, newButtonSurface + (20 * 18 * 2), newButtonSurface + (20 * 18 * 3), 0, 0x20); } - // check if Data exists for other styles male or female, and if so enable style selection buttons - if (GetFileAttributes("Appearance\\hmR00S01") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR00S01") != INVALID_FILE_ATTRIBUTES || - GetFileAttributes("Appearance\\hmR00S01.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR00S01.dat") != INVALID_FILE_ATTRIBUTES) { - // style selection buttons + if (styleButtons) { // style selection buttons fo::func::win_register_button(WinRef, 348, 199, 20, 18, -1, -1, -1, 0x512, newButtonSurface, newButtonSurface + (20 * 18), 0, 0x20); - fo::func::win_register_button(WinRef, 373, 199, 20, 18, -1, -1, -1, 0x514, newButtonSurface + (20 * 18 * 2), newButtonSurface + (20 * 18 * 3), 0, 0x20); + fo::func::win_register_button(WinRef, 374, 199, 20, 18, -1, -1, -1, 0x514, newButtonSurface + (20 * 18 * 2), newButtonSurface + (20 * 18 * 3), 0, 0x20); } } @@ -1172,16 +1189,16 @@ static void __declspec(naked) FixCharScrnBack() { DWORD FrmObj, FrmMaskObj; // frm objects for char screen Appearance button BYTE *FrmSurface, *FrmMaskSurface; - FrmSurface = fo::func::art_ptr_lock_data(BuildFrmId(6, 113), 0, 0, &FrmObj); + FrmSurface = fo::func::art_ptr_lock_data(BuildFrmId(fo::OBJ_TYPE_INTRFACE, 113), 0, 0, &FrmObj); // "Use Item On" window sub_draw(81, 132, 292, 376, 163, 20, FrmSurface, 640, 480, 331, 63, charScrnBackSurface, 0); // char view win sub_draw(79, 31, 292, 376, 154, 228, FrmSurface, 640, 480, 331, 32, charScrnBackSurface, 0); // upper char view win sub_draw(79, 30, 292, 376, 158, 236, FrmSurface, 640, 480, 331, 195, charScrnBackSurface, 0); // lower char view win fo::func::art_ptr_unlock(FrmObj); // Sexoff Frm - FrmSurface = fo::func::art_ptr_lock_data(BuildFrmId(6, 188), 0, 0, &FrmObj); + FrmSurface = fo::func::art_ptr_lock_data(BuildFrmId(fo::OBJ_TYPE_INTRFACE, 188), 0, 0, &FrmObj); // Sex button mask frm - FrmMaskSurface = fo::func::art_ptr_lock_data(BuildFrmId(6, 187), 0, 0, &FrmMaskObj); + FrmMaskSurface = fo::func::art_ptr_lock_data(BuildFrmId(fo::OBJ_TYPE_INTRFACE, 187), 0, 0, &FrmMaskObj); sub_draw(80, 28, 80, 32, 0, 0, FrmMaskSurface, 80, 32, 0, 0, FrmSurface, 0x39); // mask for style and race buttons fo::func::art_ptr_unlock(FrmMaskObj); @@ -1208,12 +1225,12 @@ static void __declspec(naked) FixCharScrnBack() { fo::func::art_ptr_unlock(FrmObj); // frm background for char screen Appearance button - FrmSurface = fo::func::art_ptr_lock_data(BuildFrmId(6, 174), 0, 0, &FrmObj); // Pickchar frm - sub_draw(69, 20, 640, 480, 282, 320, FrmSurface, 640, 480, 337, 37, charScrnBackSurface, 0); // button backround top - sub_draw(69, 20, 640, 480, 282, 320, FrmSurface, 640, 480, 337, 199, charScrnBackSurface, 0); // button backround bottom - sub_draw(47, 16, 640, 480, 94, 394, FrmSurface, 640, 480, 347, 39, charScrnBackSurface, 0); // cover buttons pics top - sub_draw(47, 16, 640, 480, 94, 394, FrmSurface, 640, 480, 347, 201, charScrnBackSurface, 0); // cover buttons pics bottom - fo::func::art_ptr_unlock(FrmObj); + if (fo::var::glblmode && (styleButtons || raceButtons)) { + FrmSurface = fo::func::art_ptr_lock_data(BuildFrmId(fo::OBJ_TYPE_INTRFACE, 174), 0, 0, &FrmObj); // Pickchar frm + if (raceButtons) sub_draw(69, 20, 640, 480, 281, 319, FrmSurface, 640, 480, 337, 36, charScrnBackSurface, 0); // button backround top + if (styleButtons) sub_draw(69, 20, 640, 480, 281, 319, FrmSurface, 640, 480, 337, 198, charScrnBackSurface, 0); // button backround bottom + fo::func::art_ptr_unlock(FrmObj); + } FrmSurface = nullptr; } @@ -1265,10 +1282,10 @@ static void __declspec(naked) CharScrnEnd() { //////////////////////////////////////////////////////////////////////FIX FUNCTIONS////////////////////////////////////////////////////////////////// -// Adjust PC SFX acm name. Skip Underscore char at the start of PC App Name +// Adjust PC SFX acm name. Skip Underscore char at the start of PC frm file name static void __declspec(naked) FixPcSFX() { __asm { - cmp byte ptr ds:[ebx], 0x5F; // check if Name begins with an '_' character + cmp byte ptr ds:[ebx], '_'; // check if Name begins with an 0x5F character jne endFunc; inc ebx; // shift address to next char endFunc: @@ -1312,17 +1329,38 @@ static void __declspec(naked) op_obj_art_fid_hack() { using namespace Fields; __asm { mov esi, [edi + artFid]; - push ecx; - call PartyControl::RealDudeObject; - pop ecx; - cmp eax, edi; // object is dude? - jnz skip; + mov eax, [edi + protoId]; + cmp eax, PID_Player; + jne skip; + mov eax, esi; + and eax, 0xFFF; // LST index + cmp eax, critterListSize; + jle skip; sub esi, critterListSize; // fix hero FrmID skip: jmp op_obj_art_fid_Ret; } } +static void __declspec(naked) op_metarule3_hook() { + using namespace Fields; + __asm { + mov edi, [esp + 0x4C - 0x44 + 8]; // source + cmp edi, ds:[FO_VAR_obj_dude]; + jne skip; + mov edi, [edi + protoId]; + cmp edi, PID_Player; + jne skip; + mov edi, edx; // edx = set fid number + and edi, 0xFFF; + cmp edi, critterListSize; + jg skip; + add edx, critterListSize; +skip: + jmp fo::funcoffs::art_id_; + } +} + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Load Appearance data from GCD file @@ -1361,15 +1399,26 @@ static void EnableHeroAppearanceMod() { HeroAppearance::appModEnabled = true; // setup paths - heroPathPtr = new fo::PathNode; - racePathPtr = new fo::PathNode; - heroPathPtr->path = new char[64]; - racePathPtr->path = new char[64]; + heroPathPtr[0] = new fo::PathNode(); + racePathPtr[0] = new fo::PathNode(); + heroPathPtr[0]->path = new char[64]; + racePathPtr[0]->path = new char[64]; - heroPathPtr->isDat = 0; - racePathPtr->isDat = 0; - heroPathPtr->pDat = nullptr; - racePathPtr->pDat = nullptr; + heroPathPtr[1] = new fo::PathNode(); + racePathPtr[1] = new fo::PathNode(); + heroPathPtr[1]->path = new char[64]; + racePathPtr[1]->path = new char[64]; + + // check if Data exists for other races male or female, and if so enable race selection buttons + if (GetFileAttributes("Appearance\\hmR01S00") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR01S00") != INVALID_FILE_ATTRIBUTES || + GetFileAttributes("Appearance\\hmR01S00.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR01S00.dat") != INVALID_FILE_ATTRIBUTES) { + raceButtons = true; + } + // check if Data exists for other styles male or female, and if so enable style selection buttons + if (GetFileAttributes("Appearance\\hmR00S01") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR00S01") != INVALID_FILE_ATTRIBUTES || + GetFileAttributes("Appearance\\hmR00S01.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR00S01.dat") != INVALID_FILE_ATTRIBUTES) { + styleButtons = true; + } // Check if new Appearance char scrn button pushed (editor_design_) HookCall(0x431E9D, CheckCharScrnButtons); @@ -1474,14 +1523,15 @@ static void EnableHeroAppearanceMod() { static void HeroAppearanceModExit() { if (!HeroAppearance::appModEnabled) return; - if (heroPathPtr) { - delete[] heroPathPtr->path; - delete heroPathPtr; - } - if (racePathPtr) { - delete[] racePathPtr->path; - delete racePathPtr; - } + delete[] heroPathPtr[0]->path; + delete[] heroPathPtr[1]->path; + delete heroPathPtr[0]; + delete heroPathPtr[1]; + + delete[] racePathPtr[0]->path; + delete[] racePathPtr[1]->path; + delete racePathPtr[0]; + delete racePathPtr[1]; } void HeroAppearance::init() { @@ -1489,8 +1539,12 @@ void HeroAppearance::init() { if (heroAppearanceMod > 0) { dlog("Setting up Appearance Char Screen buttons.", DL_INIT); EnableHeroAppearanceMod(); - // Hero FrmID fix for obj_art_fid script function - if (heroAppearanceMod != 2) MakeJump(0x45C5C3, op_obj_art_fid_hack); + + // Hero FrmID fix for obj_art_fid/art_change_fid_num script functions + if (heroAppearanceMod != 2) { + MakeJump(0x45C5C3, op_obj_art_fid_hack); + HookCall(0x4572BE, op_metarule3_hook); + } LoadGameHook::OnAfterNewGame() += []() { SetNewCharAppearanceGlobals(); diff --git a/sfall/Modules/HookScripts.cpp b/sfall/Modules/HookScripts.cpp index 2b9b09bc..3bf3ebed 100644 --- a/sfall/Modules/HookScripts.cpp +++ b/sfall/Modules/HookScripts.cpp @@ -141,11 +141,11 @@ DWORD _stdcall GetHSArg() { return (cArg == argCount) ? 0 : args[cArg++]; } -void _stdcall SetHSArg(DWORD id, DWORD value) { +void SetHSArg(DWORD id, DWORD value) { if (id < argCount) args[id] = value; } -DWORD* _stdcall GetHSArgs() { +DWORD* GetHSArgs() { return args; } @@ -185,7 +185,7 @@ void RegisterHook(fo::Program* script, int id, int procNum, bool specReg) { ScriptProgram *prog = GetGlobalScriptProgram(script); if (prog) { - dlog_f("Global script %08x registered as hook ID %d\n", DL_HOOK, script, id); + dlog_f("Global script: %08x registered as hook ID %d\n", DL_HOOK, script, id); HookScript hook; hook.prog = *prog; hook.callback = procNum; @@ -262,8 +262,8 @@ void HookScripts::init() { LoadGameHook::OnGameModeChange() += GameModeChangeHook; LoadGameHook::OnAfterGameStarted() += SourceUseSkillOnInit; - HookScripts::injectAllHooks = isDebug && (GetPrivateProfileIntA("Debugging", "InjectAllGameHooks", 0, ::sfall::ddrawIni) != 0); - if (HookScripts::injectAllHooks) dlogr("Injecting all game hooks", DL_HOOK); + HookScripts::injectAllHooks = isDebug && (iniGetInt("Debugging", "InjectAllGameHooks", 0, ::sfall::ddrawIni) != 0); + if (HookScripts::injectAllHooks) dlogr("Injecting all game hooks", DL_HOOK|DL_INIT); } } diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index 0a81b86b..75c0f8f1 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -88,8 +88,8 @@ public: DWORD _stdcall GetHSArgCount(); DWORD _stdcall GetHSArg(); -DWORD* _stdcall GetHSArgs(); -void _stdcall SetHSArg(DWORD id, DWORD value); +DWORD* GetHSArgs(); +void SetHSArg(DWORD id, DWORD value); void _stdcall SetHSReturn(DWORD d); // register hook by proc num (special values: -1 - use default (start) procedure, 0 - unregister) diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index 6a54abe9..febe5bac 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -328,21 +328,48 @@ skip: } } -/* Common InvenWield hook */ -static bool InvenWieldHook_Script(int flag) { - argCount = 4; - args[3] = flag; // invenwield flag +/* Common InvenWield script hooks */ +static long __fastcall InvenWieldHook_Script(fo::GameObject* critter, fo::GameObject* item, long slot, long isWield, long isRemove) { + if (!isWield) { + // for the critter, the right slot is always the active slot + if (slot == fo::INVEN_TYPE_LEFT_HAND && critter != fo::var::obj_dude) return 1; + // check the current active slot for the player + if (slot != fo::INVEN_TYPE_WORN && critter == fo::var::obj_dude) { + long _slot = (slot != fo::INVEN_TYPE_LEFT_HAND); + if (_slot != fo::var::itemCurrentItem) return 1; // item in non-active slot + } + } + BeginHook(); + args[0] = (DWORD)critter; + args[1] = (DWORD)item; + args[2] = slot; + args[3] = isWield; // unwield/wield event + args[4] = isRemove; + + argCount = 5; + RunHookScript(HOOK_INVENWIELD); + + long result = (cRet == 0 || rets[0] == -1); + EndHook(); + + return result; // 1 - use engine handler +} + +static __declspec(noinline) bool InvenWieldHook_ScriptPart(long isWield, long isRemove = 0) { + args[3] = isWield; // unwield/wield event + args[4] = isRemove; + + argCount = 5; RunHookScript(HOOK_INVENWIELD); bool result = (cRet == 0 || rets[0] == -1); EndHook(); - return result; // True - use engine handler + return result; // true - use engine handler } static void __declspec(naked) InvenWieldFuncHook() { - using namespace fo; __asm { HookBegin; mov args[0], eax; // critter @@ -350,19 +377,17 @@ static void __declspec(naked) InvenWieldFuncHook() { mov args[8], ebx; // slot pushad; } - // right hand slot? - if (args[2] != INVEN_TYPE_RIGHT_HAND && GetItemType((GameObject*)args[1]) != item_type_armor) { - args[2] = INVEN_TYPE_LEFT_HAND; + if (args[2] != fo::INVEN_TYPE_RIGHT_HAND && fo::GetItemType((fo::GameObject*)args[1]) != fo::item_type_armor) { + args[2] = fo::INVEN_TYPE_LEFT_HAND; } - - InvenWieldHook_Script(1); // wield flag + InvenWieldHook_ScriptPart(1); // wield event __asm { test al, al; popad; jz skip; - jmp funcoffs::invenWieldFunc_; + jmp fo::funcoffs::invenWieldFunc_; skip: mov eax, -1; retn; @@ -373,18 +398,18 @@ skip: static void __declspec(naked) InvenUnwieldFuncHook() { __asm { HookBegin; - mov args[0], eax; // critter - mov args[8], edx; // slot + mov args[0], eax; // critter + mov args[8], edx; // slot pushad; } - // set slot if (args[2] == 0) { // left hand slot? args[2] = fo::INVEN_TYPE_LEFT_HAND; } - args[1] = (DWORD)fo::GetItemPtrSlot((fo::GameObject*)args[0], (fo::InvenType)args[2]); // get item + // get item + args[1] = (DWORD)fo::GetItemPtrSlot((fo::GameObject*)args[0], (fo::InvenType)args[2]); - InvenWieldHook_Script(0); // unwield flag + InvenWieldHook_ScriptPart(0); // unwield event __asm { test al, al; @@ -398,14 +423,14 @@ skip: } static void __declspec(naked) CorrectFidForRemovedItemHook() { + using namespace fo::ObjectFlag; __asm { HookBegin; mov args[0], eax; // critter mov args[4], edx; // item mov args[8], ebx; // item flag - pushad; + pushadc; } - // set slot if (args[2] & fo::ObjectFlag::Right_Hand) { // right hand slot args[2] = fo::INVEN_TYPE_RIGHT_HAND; @@ -414,21 +439,138 @@ static void __declspec(naked) CorrectFidForRemovedItemHook() { } else { args[2] = fo::INVEN_TYPE_WORN; // armor slot } - - InvenWieldHook_Script(0); // unwield flag (armor by default) - + InvenWieldHook_ScriptPart(0, 1); // unwield event (armor by default) + // engine handler is not overridden __asm { - test al, al; - popad; - jz skip; + popadc; jmp fo::funcoffs::correctFidForRemovedItem_; + } +} + +static void __declspec(naked) item_drop_all_hack() { + using namespace fo::ObjectFlag; + __asm { + mov ecx, 1; + push eax; + mov [esp + 0x40 - 0x2C + 8], ecx; // itemIsEquipped + push ecx; // remove event + push 0; // unwield event + inc ecx; // INVEN_TYPE_LEFT_HAND (2) + test ah, Left_Hand >> 24; + jnz skip; + test ah, Worn >> 24; + setz cl; // set INVEN_TYPE_WORN or INVEN_TYPE_RIGHT_HAND skip: - mov eax, -1; + push ecx; // slot + mov edx, esi; // item + mov ecx, edi; // critter + call InvenWieldHook_Script; + //mov [esp + 0x40 - 0x2C + 8], eax; // itemIsEquipped (eax - hook return result) + pop eax; retn; } } -void AdjustFidHook(DWORD vanillaFid) { +static bool hookInvenWieldIsInject = false; + +// called from bugfixes for obj_drop_ +void __declspec(naked) InvenUnwield_HookDrop() { // ecx - critter, edx - item + using namespace fo; + using namespace Fields; + using namespace ObjectFlag; + __asm { + cmp hookInvenWieldIsInject, 1; + je runHook; + retn; +runHook: + pushadc; + mov eax, INVEN_TYPE_LEFT_HAND; + test byte ptr [edx + flags + 3], Left_Hand >> 24; + jnz isLeft; + test byte ptr [edx + flags + 3], Worn >> 24; + setz al; // set INVEN_TYPE_WORN or INVEN_TYPE_RIGHT_HAND +isLeft: + push 1; // remove event + push 0; // unwield event + push eax; // slot + call InvenWieldHook_Script; // ecx - critter, edx - item + // engine handler is not overridden + popadc; + retn; + } +} + +// called from bugfixes for op_move_obj_inven_to_obj_ +void __declspec(naked) InvenUnwield_HookMove() { // eax - item, edx - critter + __asm { + cmp hookInvenWieldIsInject, 1; + je runHook; + retn; +runHook: + pushadc; + mov ecx, edx; + mov edx, eax; + push 1; // remove event + xor eax, eax; + push eax; // unwield event + push eax; // slot + call InvenWieldHook_Script; // ecx - critter, edx - item + // engine handler is not overridden + popadc; + retn; + } +} + +// called when unwelding dude weapon and armor +static void __declspec(naked) op_move_obj_inven_to_obj_hook() { + using namespace fo; + using namespace ObjectFlag; + __asm { + cmp eax, ds:[FO_VAR_obj_dude]; + je runHook; + jmp fo::funcoffs::item_move_all_; +runHook: + push eax; + push edx; + mov ecx, eax; // keep source + mov edx, ds:[FO_VAR_itemCurrentItem]; // get player's active slot + test edx, edx; + jz left; + call fo::funcoffs::inven_right_hand_; + jmp skip; +left: + call fo::funcoffs::inven_left_hand_; +skip: + test eax, eax; + jz noWeapon; + push 1; // remove event + push 0; // unwield event + mov ebx, INVEN_TYPE_LEFT_HAND; + sub ebx, edx; + push ebx; // slot: INVEN_TYPE_LEFT_HAND or INVEN_TYPE_RIGHT_HAND + mov edx, eax; // weapon + mov ebx, ecx; // keep source + call InvenWieldHook_Script; // ecx - source + // engine handler is not overridden +noWeapon: + mov edx, [esp + 0x30 - 0x20 + 12]; // armor + test edx, edx; + jz noArmor; + xor eax, eax; + push 1; // remove event + push eax; // unwield event + push eax; // slot: INVEN_TYPE_WORN + mov ecx, ebx; // source + call InvenWieldHook_Script; + // engine handler is not overridden +noArmor: + pop edx; + pop eax; + jmp fo::funcoffs::item_move_all_; + } +} + +static void AdjustFidHook(DWORD vanillaFid) { if (!HookScripts::HookHasScript(HOOK_ADJUSTFID)) return; BeginHook(); @@ -474,7 +616,7 @@ void Inject_InventoryMoveHook() { MakeCall(0x49B660, PickupObjectHack); SafeWrite32(0x49B665, 0x850FD285); // test edx, edx SafeWrite32(0x49B669, 0xC2); // jnz 0x49B72F - SafeWrite8(0x49B66E, 0xFE); // cmp edi > cmp esi + SafeWrite8(0x49B66E, 0xFE); // cmp edi > cmp esi HookCall(0x471457, InvenPickupHook); } @@ -493,6 +635,26 @@ void Inject_InvenWieldHook() { 0x45680C, // op_rm_obj_from_inven_ 0x45C4EA // op_move_obj_inven_to_obj_ }); + HookCall(0x45C4F6, op_move_obj_inven_to_obj_hook); + MakeCall(0x4778AF, item_drop_all_hack, 3); + + hookInvenWieldIsInject = true; +} + +// internal function implementation with hook +long CorrectFidForRemovedItem_wHook(fo::GameObject* critter, fo::GameObject* item, long flags) { + long result = 1; + if (!hooks[HOOK_INVENWIELD].empty()) { + long slot = fo::INVEN_TYPE_WORN; + if (flags & fo::ObjectFlag::Right_Hand) { // right hand slot + slot = fo::INVEN_TYPE_RIGHT_HAND; + } else if (flags & fo::ObjectFlag::Left_Hand) { // left hand slot + slot = fo::INVEN_TYPE_LEFT_HAND; + } + result = InvenWieldHook_Script(critter, item, slot, 0, 0); + } + if (result) fo::func::correctFidForRemovedItem(critter, item, flags); + return result; } void InitInventoryHookScripts() { diff --git a/sfall/Modules/HookScripts/InventoryHs.h b/sfall/Modules/HookScripts/InventoryHs.h index 832f5a78..b8f30750 100644 --- a/sfall/Modules/HookScripts/InventoryHs.h +++ b/sfall/Modules/HookScripts/InventoryHs.h @@ -13,4 +13,8 @@ void Inject_SwitchHandHook(); void Inject_InventoryMoveHook(); void Inject_InvenWieldHook(); +long CorrectFidForRemovedItem_wHook(fo::GameObject* critter, fo::GameObject* item, long flags); +void InvenUnwield_HookDrop(); +void InvenUnwield_HookMove(); + } diff --git a/sfall/Modules/Input.cpp b/sfall/Modules/Input.cpp index aacffd78..1dbe37b7 100644 --- a/sfall/Modules/Input.cpp +++ b/sfall/Modules/Input.cpp @@ -11,7 +11,7 @@ namespace sfall { void Input::init() { - //if(GetConfigInt("Input", "Enable", 0)) { + //if (GetConfigInt("Input", "Enable", 0)) { dlog("Applying input patch.", DL_INIT); SafeWriteStr(0x50FB70, "ddraw.dll"); ::sfall::availableGlobalScriptTypes |= 1; diff --git a/sfall/Modules/Interface.cpp b/sfall/Modules/Interface.cpp index 86b9b894..01fdc574 100644 --- a/sfall/Modules/Interface.cpp +++ b/sfall/Modules/Interface.cpp @@ -84,7 +84,7 @@ static void ActionPointsBarPatch() { if (hrpVersionValid && !_stricmp((const char*)HRPAddressOffset(0x39358), "HR_IFACE_%i.frm")) { SafeWriteStr(HRPAddressOffset(0x39363), "E.frm"); // patching HRP } else { - dlog(" Incorrect HRP version!", DL_INIT); + dlogr(" Incorrect HRP version!", DL_INIT); return; } LoadGameHook::OnAfterGameInit() += APBarRectPatch; @@ -102,6 +102,51 @@ static void ActionPointsBarPatch() { dlogr(" Done", DL_INIT); } +static long costAP = -1; +static void __declspec(naked) intface_redraw_items_hack0() { + __asm { + sub eax, esi; + shl eax, 4; + // + cmp dword ptr [esp + 0x80 - 0x5C + 4], 99; // art width + jg newArt; + mov edx, 10; // width + retn; +newArt: + mov costAP, edx; + cmp edx, 10; + je noShift; + ja skip; + mov edx, 10; // width + retn; +skip: + sub edx, 10; + imul edx, 5; // shift + add ebx, edx; // add width shift to 'from' +noShift: + mov edx, 15; // width + retn; + } +} + +static void __declspec(naked) intface_redraw_items_hack1() { + __asm { + mov edx, 10; + cmp costAP, edx; + jl skip; + add edx, 5; // width 15 +skip: + retn; + } +} + +static void DrawActionPointsNumber() { + MakeCall(0x4604B0, intface_redraw_items_hack0); + MakeCall(0x460504, intface_redraw_items_hack1); + SafeWrite16(0x4604D4, 0x9052); // push 10 > push edx + SafeWrite8(0x46034B, 20); // draw up to 19 AP +} + ////////////////////////////// WORLDMAP INTERFACE ////////////////////////////// static int mapSlotsScrollMax = 27 * (17 - 7); @@ -412,7 +457,13 @@ static void WorldmapViewportPatch() { static void __declspec(naked) wmInterfaceRefreshCarFuel_hack_empty() { __asm { - mov byte ptr [eax - 1], 14; + mov byte ptr [eax - 1], 13; + mov byte ptr [eax + 1], 13; + add eax, wmapWinWidth; + dec ebx; + mov byte ptr [eax], 14; + mov byte ptr [eax - 1], 15; + mov byte ptr [eax + 1], 15; add eax, wmapWinWidth; retn; } @@ -421,8 +472,10 @@ static void __declspec(naked) wmInterfaceRefreshCarFuel_hack_empty() { static void __declspec(naked) wmInterfaceRefreshCarFuel_hack() { __asm { mov byte ptr [eax - 1], 196; + mov byte ptr [eax + 1], 196; add eax, wmapWinWidth; - mov byte ptr [eax - 1], 14; + mov byte ptr [eax - 1], 200; + mov byte ptr [eax + 1], 200; retn; } } @@ -439,10 +492,10 @@ static void WorldMapInterfacePatch() { SafeWrite32(0x4C2C92, 181); // index of DNARWOFF.FRM SafeWrite8(0x4C2D04, 0x46); // dec esi > inc esi - //if(GetConfigInt("Misc", "WorldMapCitiesListFix", 0)) { - dlog("Applying world map cities list patch.", DL_INIT); - HookCalls(ScrollCityListFix, {0x4C04B9, 0x4C04C8, 0x4C4A34, 0x4C4A3D}); - dlogr(" Done", DL_INIT); + //if (GetConfigInt("Misc", "WorldMapCitiesListFix", 0)) { + dlog("Applying world map cities list patch.", DL_INIT); + HookCalls(ScrollCityListFix, {0x4C04B9, 0x4C04C8, 0x4C4A34, 0x4C4A3D}); + dlogr(" Done", DL_INIT); //} DWORD wmSlots = GetConfigInt("Misc", "WorldMapSlots", 0); @@ -464,14 +517,57 @@ static void WorldMapInterfacePatch() { // Car fuel gauge graphics patch MakeCall(0x4C528A, wmInterfaceRefreshCarFuel_hack_empty); MakeCall(0x4C529E, wmInterfaceRefreshCarFuel_hack); + SafeWrite8(0x4C52A8, 197); + SafeWrite8(0x4C5289, 12); +} + +static void __declspec(naked) intface_rotate_numbers_hack() { + __asm { + push edi; + push ebp; + sub esp, 0x54; + mov edi, 0x460BA6; + // ebx - old value, ecx - new value + cmp ebx, ecx; + je end; + mov ebx, ecx; + jg decrease; + dec ebx; +end: + jmp edi; +decrease: + test ecx, ecx; + jl negative; + inc ebx; + jmp edi; +negative: + xor ebx, ebx; + jmp edi; + } +} + +static void SpeedInterfaceCounterAnimsPatch() { + switch (GetConfigInt("Misc", "SpeedInterfaceCounterAnims", 0)) { + case 1: + dlog("Applying SpeedInterfaceCounterAnims patch.", DL_INIT); + MakeJump(0x460BA1, intface_rotate_numbers_hack); + dlogr(" Done", DL_INIT); + break; + case 2: + dlog("Applying SpeedInterfaceCounterAnims patch (Instant).", DL_INIT); + SafeWrite32(0x460BB6, 0xDB319090); // xor ebx, ebx + dlogr(" Done", DL_INIT); + break; + } } void Interface::init() { if (GetConfigInt("Interface", "ActionPointsBar", 0)) { ActionPointsBarPatch(); } - + DrawActionPointsNumber(); WorldMapInterfacePatch(); + SpeedInterfaceCounterAnimsPatch(); } void Interface::exit() { diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index cf4c6038..233852aa 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -543,9 +543,18 @@ static void __declspec(naked) op_inven_unwield_hook() { push 0x505AFC; // "But is already Inactive (Dead/Stunned/Invisible)" call fo::funcoffs::debug_printf_; add esp, 4; +end: retn; skip: - jmp fo::funcoffs::inven_unwield_; + call fo::funcoffs::inven_unwield_; + // update interface slot + cmp ebx, ds:[FO_VAR_obj_dude]; + jne end; + xor eax, eax; // no animate + mov ebx, eax; + dec ebx; // modeRight (-1) + mov edx, ebx; // modeLeft (-1) + jmp fo::funcoffs::intface_update_items_; } } @@ -569,26 +578,26 @@ skip: DWORD __stdcall Inventory::adjust_fid_replacement() { DWORD fid; if (fo::var::inven_dude->TypeFid() == fo::OBJ_TYPE_CRITTER) { - DWORD frameNum; + DWORD indexNum; DWORD weaponAnimCode = 0; if (PartyControl::IsNpcControlled()) { // if NPC is under control, use current FID of critter - frameNum = fo::var::inven_dude->artFid & 0xFFF; + indexNum = fo::var::inven_dude->artFid & 0xFFF; } else { // vanilla logic: - frameNum = fo::var::art_vault_guy_num; + indexNum = fo::var::art_vault_guy_num; auto critterPro = fo::GetProto(fo::var::inven_pid); if (critterPro != nullptr) { - frameNum = critterPro->fid & 0xFFF; + indexNum = critterPro->fid & 0xFFF; } if (fo::var::i_worn != nullptr) { auto armorPro = fo::GetProto(fo::var::i_worn->protoId); - DWORD armorFrameNum = fo::func::stat_level(fo::var::inven_dude, fo::STAT_gender) == fo::GENDER_FEMALE - ? armorPro->item.armor.femaleFrameNum - : armorPro->item.armor.maleFrameNum; + DWORD armorFid = fo::func::stat_level(fo::var::inven_dude, fo::STAT_gender) == fo::GENDER_FEMALE + ? armorPro->item.armor.femaleFID + : armorPro->item.armor.maleFID; - if (armorFrameNum != -1) { - frameNum = armorFrameNum; + if (armorFid != -1) { + indexNum = armorFid; } } } @@ -602,7 +611,7 @@ DWORD __stdcall Inventory::adjust_fid_replacement() { weaponAnimCode = itemPro->item.weapon.animationCode; } } - fid = fo::func::art_id(fo::OBJ_TYPE_CRITTER, frameNum, 0, weaponAnimCode, 0); + fid = fo::func::art_id(fo::OBJ_TYPE_CRITTER, indexNum, 0, weaponAnimCode, 0); } else { fid = fo::var::inven_dude->artFid; } @@ -677,16 +686,15 @@ inline static void ApplyInvenApCostPatch() { onlyOnceAP = true; } -void _stdcall SetInvenApCost(int cost) { +void __fastcall Inventory::SetInvenApCost(int cost) { invenApCost = cost; if (!onlyOnceAP) ApplyInvenApCostPatch(); } -// TODO: Make GetInvenApCost() function -/*long GetInvenApCost() { - long plevel = fo::func::perk_level(fo::var::obj_dude, fo::PERK_quick_pockets); - return invenApCost - (invenApQPReduction * plevel); -}*/ +long Inventory::GetInvenApCost() { + long perkLevel = fo::func::perk_level(fo::var::obj_dude, fo::PERK_quick_pockets); + return invenApCost - (invenApQPReduction * perkLevel); +} void InventoryReset() { invenApCost = invenApCostDef; @@ -801,7 +809,7 @@ void Inventory::init() { // Check the DAM_KNOCKED_OUT flag for wield_obj_critter/inven_unwield script functions // Note: the flag is not checked for the metarule(METARULE_INVEN_UNWIELD_WHO, x) function - HookCall(0x45B0CE, op_inven_unwield_hook); + HookCall(0x45B0CE, op_inven_unwield_hook); // with fix to update interface slot after unwielding HookCall(0x45693C, op_wield_obj_critter_hook); } diff --git a/sfall/Modules/Inventory.h b/sfall/Modules/Inventory.h index c7ce19e1..798ddd67 100644 --- a/sfall/Modules/Inventory.h +++ b/sfall/Modules/Inventory.h @@ -33,9 +33,11 @@ public: static Delegate& OnAdjustFid(); static DWORD __stdcall adjust_fid_replacement(); + + static long GetInvenApCost(); + static void __fastcall SetInvenApCost(int cost); }; -void _stdcall SetInvenApCost(int cost); DWORD __stdcall sf_item_total_size(fo::GameObject* critter); } diff --git a/sfall/Modules/Karma.cpp b/sfall/Modules/Karma.cpp index 2dcbc874..39579866 100644 --- a/sfall/Modules/Karma.cpp +++ b/sfall/Modules/Karma.cpp @@ -77,7 +77,7 @@ void Karma::DisplayKarma(int value) { fo::func::display_print(buf); } -void ApplyDisplayKarmaChangesPatch() { +static void ApplyDisplayKarmaChangesPatch() { displayKarmaChanges = GetConfigInt("Misc", "DisplayKarmaChanges", 0) != 0; if (displayKarmaChanges) { dlog("Applying display karma changes patch.", DL_INIT); @@ -88,18 +88,20 @@ void ApplyDisplayKarmaChangesPatch() { } } -void ApplyKarmaFRMsPatch() { +static void ApplyKarmaFRMsPatch() { auto karmaFrmList = GetConfigList("Misc", "KarmaFRMs", "", 512); - if (karmaFrmList.size() > 0) { + size_t countFrm = karmaFrmList.size(); + if (countFrm) { dlog("Applying karma FRM patch.", DL_INIT); - auto karmaPointsList = GetConfigList("Misc", "KarmaPoints", "", 512); - karmaFrms.resize(karmaFrmList.size()); - for (size_t i = 0; i < karmaFrmList.size(); i++) { + + karmaFrms.resize(countFrm); + size_t countPoints = karmaPointsList.size(); + for (size_t i = 0; i < countFrm; i++) { karmaFrms[i].frm = atoi(karmaFrmList[i].c_str()); - karmaFrms[i].points = (karmaPointsList.size() > i) - ? atoi(karmaPointsList[i].c_str()) - : INT_MAX; + karmaFrms[i].points = (countPoints > i) + ? atoi(karmaPointsList[i].c_str()) + : INT_MAX; } HookCall(0x4367A9, DrawInfoWin_hook); diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index bdb88b42..5138b5ee 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -25,6 +25,7 @@ #include "AI.h" #include "BugFixes.h" +#include "CritterStats.h" #include "ExtraSaveSlots.h" #include "FileSystem.h" #include "HeroAppearance.h" @@ -34,7 +35,6 @@ #include "Perks.h" #include "ScriptExtender.h" #include "Scripting\Arrays.h" -#include "Stats.h" #include "Worldmap.h" #include "LoadGameHook.h" @@ -53,6 +53,7 @@ namespace sfall popadc \ } +static Delegate<> onBeforeGameInit; static Delegate<> onGameInit; static Delegate<> onAfterGameInit; static Delegate<> onGameExit; @@ -142,7 +143,7 @@ static void _stdcall SaveGame2() { h = CreateFileA(buf, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); if (h != INVALID_HANDLE_VALUE) { Worldmap::SaveData(h); - Stats::SaveStatData(h); + CritterStats::SaveStatData(h); CloseHandle(h); } else { goto errorSave; @@ -241,7 +242,7 @@ static bool LoadGame_Before() { dlogr("Loading data from sfalldb.sav...", DL_MAIN); h = CreateFileA(buf, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); if (h != INVALID_HANDLE_VALUE) { - if (Worldmap::LoadData(h) || Stats::LoadStatData(h)) goto errorLoad; + if (Worldmap::LoadData(h) || CritterStats::LoadStatData(h)) goto errorLoad; CloseHandle(h); } else { dlogr("Cannot open sfalldb.sav.", DL_MAIN); @@ -331,6 +332,10 @@ static void __declspec(naked) main_load_new_hook() { } static void __stdcall GameInitialization() { + onBeforeGameInit.invoke(); +} + +static void __stdcall game_init_hook() { onGameInit.invoke(); } @@ -640,8 +645,9 @@ void LoadGameHook::init() { saveSfallDataFailMsg = Translate("sfall", "SaveSfallDataFail", "ERROR saving extended savegame information! Check if other programs interfere with savegame files/folders and try again!"); - HookCalls(main_init_system_hook, {0x4809BA}); - HookCalls(main_load_new_hook, {0x480AAE}); + HookCall(0x4809BA, main_init_system_hook); + HookCall(0x4426A6, game_init_hook); + HookCall(0x480AAE, main_load_new_hook); HookCalls(LoadGame_hook, {0x443AE4, 0x443B89, 0x480B77, 0x48FD35}); SafeWrite32(0x5194C0, (DWORD)&EndLoadHook); HookCalls(SaveGame_hook, {0x443AAC, 0x443B1C, 0x48FCFF}); @@ -663,7 +669,7 @@ void LoadGameHook::init() { HookCalls(before_game_exit_hook, {0x480ACE, 0x480BC7}); HookCalls(after_game_exit_hook, {0x480AEB, 0x480BE4}); HookCalls(game_close_hook, { - 0x480CA7, // gnw_main_ + 0x480CA7, // gnw_main_ //0x480D45 // main_exit_system_ (never called) }); // game modes @@ -693,6 +699,10 @@ void LoadGameHook::init() { HookCall(0x477067, exit_move_timer_win_Hook); } +Delegate<>& LoadGameHook::OnBeforeGameInit() { + return onBeforeGameInit; +} + Delegate<>& LoadGameHook::OnGameInit() { return onGameInit; } diff --git a/sfall/Modules/LoadGameHook.h b/sfall/Modules/LoadGameHook.h index 9aaa0402..a93b6f8c 100644 --- a/sfall/Modules/LoadGameHook.h +++ b/sfall/Modules/LoadGameHook.h @@ -28,8 +28,11 @@ class LoadGameHook : public Module { public: const char* name() { return "LoadGameHook"; } void init(); - - // Invoked before game initialization (game_init_ was called). + + // Invoked before game initialization (main_init_system_ was called) + static Delegate<>& OnBeforeGameInit(); + + // Invoked during game initialization (game_init_ was called). static Delegate<>& OnGameInit(); // Invoked when the game has been initialized diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 82af8a54..2f140a34 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -1,20 +1,20 @@ /* -* sfall -* Copyright (C) 2008-2017 The sfall team -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ + * sfall + * Copyright (C) 2008-2017 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" @@ -162,9 +162,8 @@ static void __stdcall InitExtraPatches() { patchFiles.shrink_to_fit(); } -static void __fastcall game_init_databases_hook() { - fo::PathNode* master_patches; - __asm mov master_patches, eax; // eax = _master_db_handle +static void __fastcall game_init_databases_hook() { // eax = _master_db_handle + fo::PathNode* master_patches = (fo::PathNode*)fo::var::master_db_handle; if (!patchFiles.empty()) InitExtraPatches(); @@ -228,6 +227,15 @@ static void GetExtraPatches() { } } +static void MultiPatchesPatch() { + //if (GetConfigInt("Misc", "MultiPatches", 0)) { + dlog("Applying load multiple patches patch.", DL_INIT); + SafeWrite8(0x444354, 0x90); // Change step from 2 to 1 + SafeWrite8(0x44435C, 0xC4); // Disable check + dlogr(" Done", DL_INIT); + //} +} + ////////////////////////////// SAVE PARTY MEMBER PROTOTYPES ////////////////////////////// static void __fastcall AddSavPrototype(long pid) { @@ -371,6 +379,7 @@ void LoadOrder::init() { patchFiles.push_back("sfall.dat"); GetExtraPatches(); + MultiPatchesPatch(); if (GetConfigInt("Misc", "DataLoadOrderPatch", 1)) { dlog("Applying data load order patch.", DL_INIT); diff --git a/sfall/Modules/MainMenu.cpp b/sfall/Modules/MainMenu.cpp index a3c03044..954bf94a 100644 --- a/sfall/Modules/MainMenu.cpp +++ b/sfall/Modules/MainMenu.cpp @@ -126,7 +126,8 @@ void MainMenu::init() { if (OverrideColour & 0xFF) { OverrideColour &= 0x00FF00FF; OverrideColour |= 0x06000000; - SafeWrite32(0x481748, (DWORD)&OverrideColour); + unsigned char flags = static_cast((OverrideColour & 0xFF0000) >> 16); + if (!(flags & 1)) SafeWrite32(0x481748, (DWORD)&OverrideColour); } OverrideColour2 = GetConfigInt("Misc", "MainMenuBigFontColour", 0) & 0xFF; if (OverrideColour2) SafeWrite32(0x481906, (DWORD)&OverrideColour2); diff --git a/sfall/Modules/Message.cpp b/sfall/Modules/Message.cpp index dd79f94c..9bb7c72c 100644 --- a/sfall/Modules/Message.cpp +++ b/sfall/Modules/Message.cpp @@ -58,6 +58,29 @@ static std::vector msgFileList; static long msgNumCounter = 0x3000; +// Loads the msg file from the 'english' folder if it does not exist in the current language directory +static void __declspec(naked) message_load_hook() { + __asm { + mov ebx, edx; // keep mode + mov ecx, eax; // keep buf + call fo::funcoffs::db_fopen_; + test eax, eax; + jz noFile; + retn; +noFile: + push ebp; // file + push 0x500208; // "english" + push 0x50B7D0; // "text" + push 0x50B7D8; // "%s\%s\%s" + push ecx; // buf + call fo::funcoffs::sprintf_; + add esp, 20; + mov edx, ebx; + mov eax, ecx; + jmp fo::funcoffs::db_fopen_; + } +} + fo::MessageNode *GetMsgNode(fo::MessageList *msgList, int msgRef) { if (msgList != nullptr && msgList->numMsgs > 0) { fo::MessageNode *msgNode = msgList->nodes; @@ -127,11 +150,11 @@ long Message::AddExtraMsgFile(const char* msgName, long msgNumber) { fo::MessageList* list = new fo::MessageList(); if (!fo::func::message_load(list, path.c_str())) { // change current language folder - path.insert(0, "..\\english\\"); - if (!fo::func::message_load(list, path.c_str())) { + //path.insert(0, "..\\english\\"); + //if (!fo::func::message_load(list, path.c_str())) { delete list; return -2; - } + //} } if (msgNumber == 0) msgNumber = msgNumCounter++; gExtraGameMsgLists.emplace(msgNumber, list); @@ -150,21 +173,30 @@ static void ClearScriptAddedExtraGameMsg() { // C++11 msgNumCounter = 0x3000; } +static void FallbackEnglishLoadMsgFiles() { + char value[128]; + if (fo::func::get_game_config_string(value, "system", "language") && _stricmp(value, "english") != 0) { + HookCall(0x484B18, message_load_hook); + } +} + static void ClearReadExtraGameMsgFiles() { for (auto it = gExtraGameMsgLists.begin(); it != gExtraGameMsgLists.end(); ++it) { fo::func::message_exit(it->second.get()); } - //gExtraGameMsgLists.clear(); } void Message::init() { msgFileList = GetConfigList("Misc", "ExtraGameMsgFileList", "", 512); + + LoadGameHook::OnGameInit() += FallbackEnglishLoadMsgFiles; LoadGameHook::OnBeforeGameStart() += ReadExtraGameMsgFiles; + LoadGameHook::OnBeforeGameClose() += ClearReadExtraGameMsgFiles; LoadGameHook::OnGameReset() += ClearScriptAddedExtraGameMsg; } void Message::exit() { - ClearReadExtraGameMsgFiles(); + //gExtraGameMsgLists.clear(); } } diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index b5535f77..4990fd9a 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -71,31 +71,6 @@ c15: } } -static void __declspec(naked) intface_rotate_numbers_hack() { - __asm { - push edi - push ebp - sub esp, 0x54 - // ebx=old value, ecx=new value - cmp ebx, ecx - je end - mov ebx, ecx - jg decrease - dec ebx - jmp end -decrease: - test ecx, ecx - jl negative - inc ebx - jmp end -negative: - xor ebx, ebx -end: - push 0x460BA6 - retn - } -} - static void __declspec(naked) register_object_take_out_hack() { __asm { push ecx @@ -165,69 +140,6 @@ static void __declspec(naked) ReloadHook() { } } - -static const DWORD CorpseHitFix2_continue_loop1 = 0x48B99B; -static void __declspec(naked) CorpseHitFix2() { - __asm { - push eax; - mov eax, [eax]; - call fo::funcoffs::critter_is_dead_; // found some object, check if it's a dead critter - test eax, eax; - pop eax; - jz really_end; // if not, allow breaking the loop (will return this object) - jmp CorpseHitFix2_continue_loop1; // otherwise continue searching - -really_end: - mov eax, [eax]; - pop ebp; - pop edi; - pop esi; - pop ecx; - retn; - } -} - -static const DWORD CorpseHitFix2_continue_loop2 = 0x48BA0B; -// same logic as above, for different loop -static void __declspec(naked) CorpseHitFix2b() { - __asm { - mov eax, [edx]; - call fo::funcoffs::critter_is_dead_; - test eax, eax; - jz really_end; - jmp CorpseHitFix2_continue_loop2; - -really_end: - mov eax, [edx]; - pop ebp; - pop edi; - pop esi; - pop ecx; - retn; - } -} - -static const DWORD NPCStage6Fix1End = 0x493D16; -static void __declspec(naked) NPCStage6Fix1() { - __asm { - mov eax, 204; // set record size to 204 bytes - imul eax, edx; // multiply by number of NPC records in party.txt - mov ebx, eax; // copy total record size for later memset - call fo::funcoffs::mem_malloc_; // malloc the necessary memory - jmp NPCStage6Fix1End; // call memset to set all malloc'ed memory to 0 - } -} - -static const DWORD NPCStage6Fix2End = 0x49423A; -static void __declspec(naked) NPCStage6Fix2() { - __asm { - mov eax, 204; // record size is 204 bytes - imul edx, eax; // multiply by NPC number as listed in party.txt - mov eax, dword ptr ds:[FO_VAR_partyMemberAIOptions]; // get starting offset of internal NPC table - jmp NPCStage6Fix2End; // eax+edx = offset of specific NPC record - } -} - static const DWORD ScannerHookRet = 0x41BC1D; static const DWORD ScannerHookFail = 0x41BC65; static void __declspec(naked) ScannerAutomapHook() { @@ -407,21 +319,6 @@ void SkilldexImagesPatch() { dlogr(" Done", DL_INIT); } -void SpeedInterfaceCounterAnimsPatch() { - switch (GetConfigInt("Misc", "SpeedInterfaceCounterAnims", 0)) { - case 1: - dlog("Applying SpeedInterfaceCounterAnims patch.", DL_INIT); - MakeJump(0x460BA1, intface_rotate_numbers_hack); - dlogr(" Done", DL_INIT); - break; - case 2: - dlog("Applying SpeedInterfaceCounterAnims patch. (Instant)", DL_INIT); - SafeWrite32(0x460BB6, 0x90DB3190); // xor ebx, ebx - dlogr(" Done", DL_INIT); - break; - } -} - void ScienceOnCrittersPatch() { switch (GetConfigInt("Misc", "ScienceOnCritters", 0)) { case 1: @@ -486,15 +383,6 @@ void DontTurnOffSneakIfYouRunPatch() { } } -void MultiPatchesPatch() { - //if (GetConfigInt("Misc", "MultiPatches", 0)) { - dlog("Applying load multiple patches patch.", DL_INIT); - SafeWrite8(0x444354, 0x90); // Change step from 2 to 1 - SafeWrite8(0x44435C, 0xC4); // Disable check - dlogr(" Done", DL_INIT); - //} -} - void PlayIdleAnimOnReloadPatch() { if (GetConfigInt("Misc", "PlayIdleAnimOnReload", 0)) { dlog("Applying idle anim on reload patch.", DL_INIT); @@ -503,26 +391,6 @@ void PlayIdleAnimOnReloadPatch() { } } -void CorpseLineOfFireFix() { - if (GetConfigInt("Misc", "CorpseLineOfFireFix", 1)) { - dlog("Applying corpse line of fire patch.", DL_INIT); - MakeJump(0x48B994, CorpseHitFix2); - MakeJump(0x48BA04, CorpseHitFix2b); - dlogr(" Done", DL_INIT); - } -} - -void NpcStage6Fix() { - if (GetConfigInt("Misc", "NPCStage6Fix", 0)) { - dlog("Applying NPC Stage 6 Fix.", DL_INIT); - MakeJump(0x493CE9, NPCStage6Fix1); - SafeWrite8(0x494063, 6); // loop should look for a potential 6th stage - SafeWrite8(0x4940BB, 204); // move pointer by 204 bytes instead of 200 - MakeJump(0x494224, NPCStage6Fix2); - dlogr(" Done", DL_INIT); - } -} - void MotionScannerFlagsPatch() { DWORD flags; if (flags = GetConfigInt("Misc", "MotionScannerFlags", 1)) { @@ -629,8 +497,7 @@ void PipboyAvailableAtStartPatch() { switch (GetConfigInt("Misc", "PipBoyAvailableAtGameStart", 0)) { case 1: LoadGameHook::OnBeforeGameStart() += []() { - // PipBoy aquiring video - fo::var::gmovie_played_list[3] = true; + fo::var::gmovie_played_list[3] = true; // PipBoy aquiring video }; break; case 2: @@ -795,14 +662,11 @@ void MiscPatches::init() { F1EngineBehaviorPatch(); DialogueFix(); AdditionalWeaponAnimsPatch(); - MultiPatchesPatch(); AlwaysReloadMsgs(); PlayIdleAnimOnReloadPatch(); - CorpseLineOfFireFix(); SkilldexImagesPatch(); - SpeedInterfaceCounterAnimsPatch(); ScienceOnCrittersPatch(); InventoryCharacterRotationSpeedPatch(); @@ -810,7 +674,6 @@ void MiscPatches::init() { BlockCall(0x4425E6); OverrideMusicDirPatch(); - NpcStage6Fix(); BoostScriptDialogLimitPatch(); MotionScannerFlagsPatch(); EncounterTableSizePatch(); diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 116f2d9a..789bec74 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -//#include - #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "HookScripts\InventoryHs.h" @@ -280,10 +278,10 @@ static void __stdcall DisplayCantDoThat() { } // 1 skip handler, -1 don't skip -int __stdcall PartyControl::SwitchHandHook(fo::GameObject* item) { +int __fastcall PartyControl::SwitchHandHook(fo::GameObject* item) { // don't allow to use the weapon, if no art exist for it - if (isControllingNPC && fo::func::item_get_type(item) == fo::ItemType::item_type_weapon) { - int fId = fo::var::obj_dude->artFid; + if (/*isControllingNPC &&*/ fo::func::item_get_type(item) == fo::ItemType::item_type_weapon) { + int fId = fo::var::i_fid; //fo::var::obj_dude->artFid; long weaponCode = fo::AnimCodeByWeapon(item); fId = (fId & 0xFFFF0FFF) | (weaponCode << 12); if (!fo::func::art_exists(fId)) { @@ -308,15 +306,29 @@ static long __fastcall GetRealDudeTrait(fo::GameObject* source, long trait) { return fo::func::trait_level(trait); } +static void __declspec(naked) inven_pickup_hook() { + __asm { + pushadc; + mov ecx, eax; // item + call PartyControl::SwitchHandHook; + test eax, eax; + popadc; + jns skip; // eax > -1 + jmp fo::funcoffs::switch_hand_; +skip: + retn; + } +} + static void __declspec(naked) stat_pc_add_experience_hook() { __asm { cmp isControllingNPC, 0; - je skip; - add delayedExperience, esi; - retn; -skip: + jne skip; xchg esi, eax; jmp fo::funcoffs::stat_pc_add_experience_; +skip: + add delayedExperience, esi; + retn; } } @@ -324,14 +336,54 @@ skip: static void __declspec(naked) pc_flag_toggle_hook() { __asm { cmp isControllingNPC, 0; - je end; - call DisplayCantDoThat; - retn; -end: + jne near DisplayCantDoThat; jmp fo::funcoffs::pc_flag_toggle_; } } +static void __declspec(naked) intface_toggle_items_hack() { + __asm { +// cmp isControllingNPC, 0; +// jne checkArt; +// and eax, 0x0F000; +// retn; +//checkArt: + mov ebx, eax; // keep current dude fid + push edx; // weapon animation code + and ebx, 0x0F000; + shl edx, 12; + and eax, 0xFFFF0FFF; + or eax, edx; + pop edx; + call fo::funcoffs::art_exists_; + test eax, eax; + jz noArt; + mov eax, ebx; + retn; +noArt: + mov eax, 1; + sub eax, ds:[FO_VAR_itemCurrentItem]; + mov ds:[FO_VAR_itemCurrentItem], eax; // revert + call DisplayCantDoThat; + add esp, 4; // destroy return addr + pop edx; + pop ecx; + pop ebx; + retn; + } +} + +static void __declspec(naked) proto_name_hook() { + __asm { + cmp isControllingNPC, 0; + jne pcName; + jmp fo::funcoffs::critter_name_; +pcName: + lea eax, realDude.pc_name; + retn; + } +} + static void PartyControlReset() { if (realDude.obj_dude != nullptr && isControllingNPC) { RestoreRealDudeState(false); @@ -415,7 +467,9 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) { if (switchHandHookInjected) return; switchHandHookInjected = true; - if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); +// if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); + + HookCall(0x49EB09, proto_name_hook); // Gets dude perks and traits from script while controlling another NPC // WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code @@ -491,12 +545,17 @@ void PartyControl::init() { HookCall(0x454218, stat_pc_add_experience_hook); // call inside op_give_exp_points_hook HookCalls(pc_flag_toggle_hook, { 0x4124F1, 0x41279A }); + MakeCall(0x45F47C, intface_toggle_items_hack); + HookCalls(inven_pickup_hook, { // will be overwritten if HOOK_INVENTORYMOVE is injected + 0x4712E3, // left slot + 0x47136D // right slot + }); NpcAutoLevelPatch(); skipCounterAnim = (GetConfigInt("Misc", "SpeedInterfaceCounterAnims", 0) == 3); - // display party member's current level & AC & addict flag + // Display party member's current level & AC & addict flag if (GetConfigInt("Misc", "PartyMemberExtraInfo", 0)) { dlog("Applying display NPC extra info patch.", DL_INIT); HookCall(0x44926F, gdControlUpdateInfo_hook); diff --git a/sfall/Modules/PartyControl.h b/sfall/Modules/PartyControl.h index 66c761e6..97424d39 100644 --- a/sfall/Modules/PartyControl.h +++ b/sfall/Modules/PartyControl.h @@ -28,7 +28,7 @@ public: void init(); void exit() override; - static int __stdcall SwitchHandHook(fo::GameObject* item); + static int __fastcall SwitchHandHook(fo::GameObject* item); static bool IsNpcControlled(); diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index 758234b9..37dfde0b 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -29,6 +29,8 @@ namespace sfall { using namespace fo; +long Perks::PerkLevelMod = 0; + constexpr int maxNameLen = 64; // don't change size constexpr int maxDescLen = 512; // don't change size static const int descLen = 256; // maximum text length for interface @@ -45,7 +47,9 @@ static char PerkBoxTitle[33]; static const int startFakeID = 256; static DWORD addPerkMode = 2; + static bool perksReInit = false; +static int perksEnable = 0; static PerkInfo perks[PERK_count]; static TraitInfo traits[TRAIT_count]; @@ -851,60 +855,60 @@ static void PerkEngineInit() { } static void PerkSetup() { - memcpy(perks, var::perk_data, sizeof(PerkInfo) * PERK_count); // copy vanilla data - if (!perksReInit) { // _perk_data SafeWriteBatch((DWORD)perks, {0x496669, 0x496837, 0x496BAD, 0x496C41, 0x496D25}); - SafeWrite32(0x496696, (DWORD)perks + 4); - SafeWrite32(0x496BD1, (DWORD)perks + 4); - SafeWrite32(0x496BF5, (DWORD)perks + 8); - SafeWrite32(0x496AD4, (DWORD)perks + 12); + SafeWrite32(0x496696, (DWORD)&perks[0].description); + SafeWrite32(0x496BD1, (DWORD)&perks[0].description); + SafeWrite32(0x496BF5, (DWORD)&perks[0].image); + SafeWrite32(0x496AD4, (DWORD)&perks[0].ranks); } - if (perksFile[0] != '\0') { + memcpy(perks, var::perk_data, sizeof(PerkInfo) * PERK_count); // copy vanilla data + + if (perksEnable) { char num[4]; for (int i = 0; i < PERK_count; i++) { _itoa_s(i, num, 10); - if (GetPrivateProfileString(num, "Name", "", &Name[i * maxNameLen], maxNameLen - 1, perksFile)) { + if (iniGetString(num, "Name", "", &Name[i * maxNameLen], maxNameLen - 1, perksFile)) { perks[i].name = &Name[i * maxNameLen]; } - if (GetPrivateProfileString(num, "Desc", "", &Desc[i * descLen], descLen - 1, perksFile)) { + if (iniGetString(num, "Desc", "", &Desc[i * descLen], descLen - 1, perksFile)) { perks[i].description = &Desc[i * descLen]; } int value; - value = GetPrivateProfileInt(num, "Image", -99999, perksFile); + value = iniGetInt(num, "Image", -99999, perksFile); if (value != -99999) perks[i].image = value; - value = GetPrivateProfileInt(num, "Ranks", -99999, perksFile); + value = iniGetInt(num, "Ranks", -99999, perksFile); if (value != -99999) perks[i].ranks = value; - value = GetPrivateProfileInt(num, "Level", -99999, perksFile); + value = iniGetInt(num, "Level", -99999, perksFile); if (value != -99999) perks[i].levelMin = value; - value = GetPrivateProfileInt(num, "Stat", -99999, perksFile); + value = iniGetInt(num, "Stat", -99999, perksFile); if (value != -99999) perks[i].stat = value; - value = GetPrivateProfileInt(num, "StatMag", -99999, perksFile); + value = iniGetInt(num, "StatMag", -99999, perksFile); if (value != -99999) perks[i].statMod = value; - value = GetPrivateProfileInt(num, "Skill1", -99999, perksFile); + value = iniGetInt(num, "Skill1", -99999, perksFile); if (value != -99999) perks[i].skill1 = value; - value = GetPrivateProfileInt(num, "Skill1Mag", -99999, perksFile); + value = iniGetInt(num, "Skill1Mag", -99999, perksFile); if (value != -99999) perks[i].skill1Min = value; - value = GetPrivateProfileInt(num, "Type", -99999, perksFile); + value = iniGetInt(num, "Type", -99999, perksFile); if (value != -99999) perks[i].skillOperator = value; - value = GetPrivateProfileInt(num, "Skill2", -99999, perksFile); + value = iniGetInt(num, "Skill2", -99999, perksFile); if (value != -99999) perks[i].skill2 = value; - value = GetPrivateProfileInt(num, "Skill2Mag", -99999, perksFile); + value = iniGetInt(num, "Skill2Mag", -99999, perksFile); if (value != -99999) perks[i].skill2Min = value; - value = GetPrivateProfileInt(num, "STR", -99999, perksFile); + value = iniGetInt(num, "STR", -99999, perksFile); if (value != -99999) perks[i].strengthMin = value; - value = GetPrivateProfileInt(num, "PER", -99999, perksFile); + value = iniGetInt(num, "PER", -99999, perksFile); if (value != -99999) perks[i].perceptionMin = value; - value = GetPrivateProfileInt(num, "END", -99999, perksFile); + value = iniGetInt(num, "END", -99999, perksFile); if (value != -99999) perks[i].enduranceMin = value; - value = GetPrivateProfileInt(num, "CHR", -99999, perksFile); + value = iniGetInt(num, "CHR", -99999, perksFile); if (value != -99999) perks[i].charismaMin = value; - value = GetPrivateProfileInt(num, "INT", -99999, perksFile); + value = iniGetInt(num, "INT", -99999, perksFile); if (value != -99999) perks[i].intelligenceMin = value; - value = GetPrivateProfileInt(num, "AGL", -99999, perksFile); + value = iniGetInt(num, "AGL", -99999, perksFile); if (value != -99999) perks[i].agilityMin = value; - value = GetPrivateProfileInt(num, "LCK", -99999, perksFile); + value = iniGetInt(num, "LCK", -99999, perksFile); if (value != -99999) perks[i].luckMin = value; } if (perksReInit) { @@ -917,41 +921,41 @@ static void PerkSetup() { int n = 0; for (int id = PERK_count; id < startFakeID; id++) { _itoa(id, num, 10); - int ranks = GetPrivateProfileInt(num, "Ranks", -1, perksFile); + int ranks = iniGetInt(num, "Ranks", -1, perksFile); if (ranks == -1) continue; extPerks[n].data.ranks = ranks; - extPerks[n].data.image = GetPrivateProfileInt(num, "Image", -1, perksFile); - extPerks[n].data.levelMin = GetPrivateProfileInt(num, "Level", -1, perksFile); - extPerks[n].data.stat = GetPrivateProfileInt(num, "Stat", -1, perksFile); - extPerks[n].data.statMod = GetPrivateProfileInt(num, "StatMag", 0, perksFile); - extPerks[n].data.skill1 = GetPrivateProfileInt(num, "Skill1", -1, perksFile); - extPerks[n].data.skill1Min = GetPrivateProfileInt(num, "Skill1Mag", 0, perksFile); - extPerks[n].data.skillOperator = GetPrivateProfileInt(num, "Type", 0, perksFile); - extPerks[n].data.skill2 = GetPrivateProfileInt(num, "Skill2", -1, perksFile); - extPerks[n].data.skill2Min = GetPrivateProfileInt(num, "Skill2Mag", 0, perksFile); - extPerks[n].data.strengthMin = GetPrivateProfileInt(num, "STR", 0, perksFile); - extPerks[n].data.perceptionMin = GetPrivateProfileInt(num, "PER", 0, perksFile); - extPerks[n].data.enduranceMin = GetPrivateProfileInt(num, "END", 0, perksFile); - extPerks[n].data.charismaMin = GetPrivateProfileInt(num, "CHR", 0, perksFile); - extPerks[n].data.intelligenceMin = GetPrivateProfileInt(num, "INT", 0, perksFile); - extPerks[n].data.agilityMin = GetPrivateProfileInt(num, "AGL", 0, perksFile); - extPerks[n].data.luckMin = GetPrivateProfileInt(num, "LCK", 0, perksFile); + extPerks[n].data.image = iniGetInt(num, "Image", -1, perksFile); + extPerks[n].data.levelMin = iniGetInt(num, "Level", -1, perksFile); + extPerks[n].data.stat = iniGetInt(num, "Stat", -1, perksFile); + extPerks[n].data.statMod = iniGetInt(num, "StatMag", 0, perksFile); + extPerks[n].data.skill1 = iniGetInt(num, "Skill1", -1, perksFile); + extPerks[n].data.skill1Min = iniGetInt(num, "Skill1Mag", 0, perksFile); + extPerks[n].data.skillOperator = iniGetInt(num, "Type", 0, perksFile); + extPerks[n].data.skill2 = iniGetInt(num, "Skill2", -1, perksFile); + extPerks[n].data.skill2Min = iniGetInt(num, "Skill2Mag", 0, perksFile); + extPerks[n].data.strengthMin = iniGetInt(num, "STR", 0, perksFile); + extPerks[n].data.perceptionMin = iniGetInt(num, "PER", 0, perksFile); + extPerks[n].data.enduranceMin = iniGetInt(num, "END", 0, perksFile); + extPerks[n].data.charismaMin = iniGetInt(num, "CHR", 0, perksFile); + extPerks[n].data.intelligenceMin = iniGetInt(num, "INT", 0, perksFile); + extPerks[n].data.agilityMin = iniGetInt(num, "AGL", 0, perksFile); + extPerks[n].data.luckMin = iniGetInt(num, "LCK", 0, perksFile); - GetPrivateProfileString(num, "Name", "Error", extPerks[n].Name, maxNameLen - 1, perksFile); + iniGetString(num, "Name", "Error", extPerks[n].Name, maxNameLen - 1, perksFile); extPerks[n].data.name = extPerks[n].Name; - GetPrivateProfileString(num, "Desc", "Error", extPerks[n].Desc, descLen - 1, perksFile); + iniGetString(num, "Desc", "Error", extPerks[n].Desc, descLen - 1, perksFile); extPerks[n].data.description = extPerks[n].Desc; - extPerks[n].stat1 = GetPrivateProfileInt(num, "Stat1", -1, perksFile); - extPerks[n].stat1Mod = GetPrivateProfileInt(num, "Stat1Mag", 0, perksFile); - extPerks[n].stat2 = GetPrivateProfileInt(num, "Stat2", -1, perksFile); - extPerks[n].stat2Mod = GetPrivateProfileInt(num, "Stat2Mag", 0, perksFile); - extPerks[n].skill3 = GetPrivateProfileInt(num, "Skill3", -1, perksFile); - extPerks[n].skill3Mod = GetPrivateProfileInt(num, "Skill3Mod", 0, perksFile); - extPerks[n].skill4 = GetPrivateProfileInt(num, "Skill4", -1, perksFile); - extPerks[n].skill4Mod = GetPrivateProfileInt(num, "Skill4Mod", 0, perksFile); - extPerks[n].skill5 = GetPrivateProfileInt(num, "Skill5", -1, perksFile); - extPerks[n].skill5Mod = GetPrivateProfileInt(num, "Skill5Mod", 0, perksFile); + extPerks[n].stat1 = iniGetInt(num, "Stat1", -1, perksFile); + extPerks[n].stat1Mod = iniGetInt(num, "Stat1Mag", 0, perksFile); + extPerks[n].stat2 = iniGetInt(num, "Stat2", -1, perksFile); + extPerks[n].stat2Mod = iniGetInt(num, "Stat2Mag", 0, perksFile); + extPerks[n].skill3 = iniGetInt(num, "Skill3", -1, perksFile); + extPerks[n].skill3Mod = iniGetInt(num, "Skill3Mod", 0, perksFile); + extPerks[n].skill4 = iniGetInt(num, "Skill4", -1, perksFile); + extPerks[n].skill4Mod = iniGetInt(num, "Skill4Mod", 0, perksFile); + extPerks[n].skill5 = iniGetInt(num, "Skill5", -1, perksFile); + extPerks[n].skill5Mod = iniGetInt(num, "Skill5Mod", 0, perksFile); extPerks[n].id = id; ++n; extPerks.resize(n + 1); // add next 'empty' perk @@ -1117,84 +1121,82 @@ static void TraitSetup() { // _trait_data SafeWrite32(0x4B3A81, (DWORD)traits); SafeWrite32(0x4B3B80, (DWORD)traits); - SafeWrite32(0x4B3AAE, (DWORD)traits + 4); - SafeWrite32(0x4B3BA0, (DWORD)traits + 4); - SafeWrite32(0x4B3BC0, (DWORD)traits + 8); + SafeWrite32(0x4B3AAE, (DWORD)&traits[0].description); + SafeWrite32(0x4B3BA0, (DWORD)&traits[0].description); + SafeWrite32(0x4B3BC0, (DWORD)&traits[0].image); - if (perksFile[0] != '\0') { - char buf[512], num[5] = {'t'}; - char* num2 = &num[1]; - for (int i = 0; i < TRAIT_count; i++) { - _itoa_s(i, num2, 4, 10); - if (GetPrivateProfileString(num, "Name", "", &tName[i * maxNameLen], maxNameLen - 1, perksFile)) { - traits[i].name = &tName[i * maxNameLen]; - } - if (GetPrivateProfileString(num, "Desc", "", &tDesc[i * descLen], descLen - 1, perksFile)) { - traits[i].description = &tDesc[i * descLen]; - } - int value; - value = GetPrivateProfileInt(num, "Image", -99999, perksFile); - if (value != -99999) traits[i].image = value; + char buf[512], num[5] = {'t'}; + char* num2 = &num[1]; + for (int i = 0; i < TRAIT_count; i++) { + _itoa_s(i, num2, 4, 10); + if (iniGetString(num, "Name", "", &tName[i * maxNameLen], maxNameLen - 1, perksFile)) { + traits[i].name = &tName[i * maxNameLen]; + } + if (iniGetString(num, "Desc", "", &tDesc[i * descLen], descLen - 1, perksFile)) { + traits[i].description = &tDesc[i * descLen]; + } + int value; + value = iniGetInt(num, "Image", -99999, perksFile); + if (value != -99999) traits[i].image = value; - if (GetPrivateProfileStringA(num, "StatMod", "", buf, 512, perksFile) > 0) { - char *stat, *mod; - stat = strtok(buf, "|"); + if (iniGetString(num, "StatMod", "", buf, 512, perksFile) > 0) { + char *stat, *mod; + stat = strtok(buf, "|"); + mod = strtok(0, "|"); + while (stat&&mod) { + int _stat = atoi(stat), _mod = atoi(mod); + if (_stat >= 0 && _stat <= STAT_max_derived) TraitStatBonuses[_stat * TRAIT_count + i] = _mod; + stat = strtok(0, "|"); mod = strtok(0, "|"); - while (stat&&mod) { - int _stat = atoi(stat), _mod = atoi(mod); - if (_stat >= 0 && _stat <= STAT_max_derived) TraitStatBonuses[_stat * TRAIT_count + i] = _mod; - stat = strtok(0, "|"); - mod = strtok(0, "|"); - } } + } - if (GetPrivateProfileStringA(num, "SkillMod", "", buf, 512, perksFile) > 0) { - char *stat, *mod; - stat = strtok(buf, "|"); + if (iniGetString(num, "SkillMod", "", buf, 512, perksFile) > 0) { + char *stat, *mod; + stat = strtok(buf, "|"); + mod = strtok(0, "|"); + while (stat&&mod) { + int _stat = atoi(stat), _mod = atoi(mod); + if (_stat >= 0 && _stat < 18) TraitSkillBonuses[_stat * TRAIT_count + i] = _mod; + stat = strtok(0, "|"); mod = strtok(0, "|"); - while (stat&&mod) { - int _stat = atoi(stat), _mod = atoi(mod); - if (_stat >= 0 && _stat < 18) TraitSkillBonuses[_stat * TRAIT_count + i] = _mod; - stat = strtok(0, "|"); - mod = strtok(0, "|"); - } } + } - if (GetPrivateProfileInt(num, "NoHardcode", 0, perksFile)) { - disableTraits[i] = true; - switch (i) { - case TRAIT_one_hander: - HookCall(0x4245E0, BlockedTrait); - break; - case TRAIT_finesse: - HookCall(0x4248F9, BlockedTrait); - break; - case TRAIT_fast_shot: - HookCall(0x478C8A, BlockedTrait); // fast shot - HookCall(0x478E70, BlockedTrait); - break; - case TRAIT_bloody_mess: - HookCall(0x410707, BlockedTrait); - break; - case TRAIT_jinxed: - HookCall(0x42389F, BlockedTrait); - break; - case TRAIT_drug_addict: - HookCall(0x47A0CD, BlockedTrait); - HookCall(0x47A51A, BlockedTrait); - break; - case TRAIT_drug_resistant: - HookCall(0x479BE1, BlockedTrait); - HookCall(0x47A0DD, BlockedTrait); - break; - case TRAIT_skilled: - HookCall(0x43C295, BlockedTrait); - HookCall(0x43C2F3, BlockedTrait); - break; - case TRAIT_gifted: - HookCall(0x43C2A4, BlockedTrait); - break; - } + if (iniGetInt(num, "NoHardcode", 0, perksFile)) { + disableTraits[i] = true; + switch (i) { + case TRAIT_one_hander: + HookCall(0x4245E0, BlockedTrait); + break; + case TRAIT_finesse: + HookCall(0x4248F9, BlockedTrait); + break; + case TRAIT_fast_shot: + HookCall(0x478C8A, BlockedTrait); // fast shot + HookCall(0x478E70, BlockedTrait); + break; + case TRAIT_bloody_mess: + HookCall(0x410707, BlockedTrait); + break; + case TRAIT_jinxed: + HookCall(0x42389F, BlockedTrait); + break; + case TRAIT_drug_addict: + HookCall(0x47A0CD, BlockedTrait); + HookCall(0x47A51A, BlockedTrait); + break; + case TRAIT_drug_resistant: + HookCall(0x479BE1, BlockedTrait); + HookCall(0x47A0DD, BlockedTrait); + break; + case TRAIT_skilled: + HookCall(0x43C295, BlockedTrait); + HookCall(0x43C2F3, BlockedTrait); + break; + case TRAIT_gifted: + HookCall(0x43C2A4, BlockedTrait); + break; } } } @@ -1291,9 +1293,11 @@ void PerksReset() { // Reset some settable game values back to the defaults // Perk level mod - SafeWrite32(0x496880, 0x019078); + Perks::PerkLevelMod = 0; // Pyromaniac bonus SafeWrite8(0x424AB6, 5); + // Swift Learner bonus + SafeWrite32(0x4AFAE2, 100); // Restore 'Heave Ho' modify fix if (perkHeaveHoModFix) { SafeWrite8(0x478AC4, 0xBA); @@ -1442,40 +1446,47 @@ void PerksAcceptCharScreen() { } void Perks::init() { + LoadGameHook::OnGameReset() += PerksReset; + FastShotTraitFix(); for (int i = STAT_st; i <= STAT_lu; i++) SafeWrite8(GainStatPerks[i][0], (BYTE)GainStatPerks[i][1]); PerkEngineInit(); HookCall(0x442729, PerkInitWrapper); // game_init_ + if (GetConfigString("Misc", "PerksFile", "", &perksFile[2], MAX_PATH - 3)) { perksFile[0] = '.'; perksFile[1] = '\\'; - HookCall(0x44272E, TraitInitWrapper); // game_init_ + if (GetFileAttributes(perksFile) == INVALID_FILE_ATTRIBUTES) return; + + perksEnable = iniGetInt("Perks", "Enable", 1, perksFile); + if (iniGetInt("Traits", "Enable", 1, perksFile)) { + HookCall(0x44272E, TraitInitWrapper); // game_init_ + } // Engine perks settings - long enginePerkMod = GetPrivateProfileIntA("PerksTweak", "WeaponScopeRangePenalty", -1, perksFile); + long enginePerkMod = iniGetInt("PerksTweak", "WeaponScopeRangePenalty", -1, perksFile); if (enginePerkMod >= 0 && enginePerkMod != 8) SafeWrite32(0x42448E, enginePerkMod); - enginePerkMod = GetPrivateProfileIntA("PerksTweak", "WeaponScopeRangeBonus", -1, perksFile); + enginePerkMod = iniGetInt("PerksTweak", "WeaponScopeRangeBonus", -1, perksFile); if (enginePerkMod >= 2 && enginePerkMod != 5) SafeWrite32(0x424489, enginePerkMod); - enginePerkMod = GetPrivateProfileIntA("PerksTweak", "WeaponLongRangeBonus", -1, perksFile); + enginePerkMod = iniGetInt("PerksTweak", "WeaponLongRangeBonus", -1, perksFile); if (enginePerkMod >= 2 && enginePerkMod != 4) SafeWrite32(0x424474, enginePerkMod); - enginePerkMod = GetPrivateProfileIntA("PerksTweak", "WeaponAccurateBonus", -1, perksFile); + enginePerkMod = iniGetInt("PerksTweak", "WeaponAccurateBonus", -1, perksFile); if (enginePerkMod >= 0 && enginePerkMod != 20) { if (enginePerkMod > 200) enginePerkMod = 200; SafeWrite8(0x42465D, static_cast(enginePerkMod)); } - enginePerkMod = GetPrivateProfileIntA("PerksTweak", "WeaponHandlingBonus", -1, perksFile); + enginePerkMod = iniGetInt("PerksTweak", "WeaponHandlingBonus", -1, perksFile); if (enginePerkMod >= 0 && enginePerkMod != 3) { if (enginePerkMod > 10) enginePerkMod = 10; SafeWrite8(0x424636, static_cast(enginePerkMod)); SafeWrite8(0x4251CE, static_cast(-enginePerkMod)); } } - LoadGameHook::OnGameReset() += PerksReset; } } diff --git a/sfall/Modules/Perks.h b/sfall/Modules/Perks.h index 5b448286..dab65212 100644 --- a/sfall/Modules/Perks.h +++ b/sfall/Modules/Perks.h @@ -39,6 +39,8 @@ public: static DWORD HasFakeTrait(const char* name); static DWORD HasFakePerkOwner(const char* name, long objId); static DWORD HasFakeTraitOwner(const char* name, long objId); + + static long PerkLevelMod; }; void PerksEnterCharScreen(); diff --git a/sfall/Modules/Premade.cpp b/sfall/Modules/Premade.cpp index 2b7f9525..340f6a72 100644 --- a/sfall/Modules/Premade.cpp +++ b/sfall/Modules/Premade.cpp @@ -29,20 +29,26 @@ fo::PremadeChar* premade; void Premade::init() { auto premadePaths = GetConfigList("misc", "PremadePaths", "", 512); auto premadeFids = GetConfigList("misc", "PremadeFIDs", "", 512); - if (premadePaths.size() > 0 && premadeFids.size() > 0) { + if (!premadePaths.empty() && !premadeFids.empty()) { + dlog("Applying premade characters patch.", DL_INIT); int count = min(premadePaths.size(), premadeFids.size()); premade = new fo::PremadeChar[count]; for (int i = 0; i < count; i++) { auto path = "premade\\" + premadePaths[i]; - strcpy_s(premade[i].path, 20, path.c_str()); + if (path.size() > 19) { + dlog_f(" Failed: %s exceeds 11 characters\n", DL_INIT, premadePaths[i].c_str()); + return; + } + strcpy(premade[i].path, path.c_str()); premade[i].fid = atoi(premadeFids[i].c_str()); } SafeWrite32(0x51C8D4, count); SafeWrite32(0x4A7D76, (DWORD)premade); SafeWrite32(0x4A8B1E, (DWORD)premade); - SafeWrite32(0x4A7E2C, (DWORD)premade + 20); - strcpy_s((char*)0x50AF68, 20, premade[0].path); + SafeWrite32(0x4A7E2C, (DWORD)&premade[0].fid); + strcpy((char*)0x50AF68, premade[0].path); + dlogr(" Done", DL_INIT); } } diff --git a/sfall/Modules/Reputations.cpp b/sfall/Modules/Reputations.cpp index 4fe3e553..48292a40 100644 --- a/sfall/Modules/Reputations.cpp +++ b/sfall/Modules/Reputations.cpp @@ -36,18 +36,18 @@ static CityRep* repList = nullptr; void Reputations::init() { auto cityRepList = GetConfigList("Misc", "CityRepsList", "", 512); - auto count = cityRepList.size(); - if (count > 0) { - repList = new CityRep[cityRepList.size()]; + size_t count = cityRepList.size(); + if (count) { + repList = new CityRep[count]; std::vector pair; pair.reserve(2); - for (size_t i = 0; i < cityRepList.size(); i++) { - pair.clear(); + for (size_t i = 0; i < count; i++) { split(cityRepList[i], ':', std::back_inserter(pair), 2); repList[i].cityID = atoi(pair[0].c_str()); if (pair.size() >= 2) { repList[i].globalID = atoi(pair[1].c_str()); } + pair.clear(); } SafeWrite32(0x43BEA5, (DWORD)&repList[0].cityID); diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 65c61786..d6f3d5ff 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -473,6 +473,7 @@ static void PrepareGlobalScriptsListByMask() { for (int i = 0; i < count; i++) { char* name = _strlwr(filenames[i]); // name of the script in lower case + if (name[0] != 'g' || name[1] != 'l') continue; // fix bug in db_get_file_list fuction (if the script name begins with a non-Latin character) std::string baseName(name); int lastDot = baseName.find_last_of('.'); @@ -653,7 +654,7 @@ void SaveGlobals(HANDLE h) { static void ClearGlobals() { globalVars.clear(); for (array_itr it = arrays.begin(); it != arrays.end(); ++it) { - it->second.clear(); + it->second.clearArrayVar(); } arrays.clear(); savedArrays.clear(); @@ -750,7 +751,7 @@ void ScriptExtender::init() { } } - alwaysFindScripts = isDebug && (GetPrivateProfileIntA("Debugging", "AlwaysFindScripts", 0, ::sfall::ddrawIni) != 0); + alwaysFindScripts = isDebug && (iniGetInt("Debugging", "AlwaysFindScripts", 0, ::sfall::ddrawIni) != 0); if (alwaysFindScripts) dlogr("Always searching for global scripts behavior enabled.", DL_SCRIPT); MakeJump(0x4A390C, FindSidHack); diff --git a/sfall/Modules/Scripting/Arrays.cpp b/sfall/Modules/Scripting/Arrays.cpp index b2332c7b..fd1adff2 100644 --- a/sfall/Modules/Scripting/Arrays.cpp +++ b/sfall/Modules/Scripting/Arrays.cpp @@ -42,9 +42,9 @@ sArrayElement::sArrayElement(const long& other) set(other); } -void sArrayElement::clear() +void sArrayElement::clearData() { - if (strVal && type == DataType::STR) delete[] strVal; + if (type == DataType::STR) delete[] strVal; } void sArrayElement::setByType( DWORD val, DataType dataType ) @@ -64,21 +64,21 @@ void sArrayElement::setByType( DWORD val, DataType dataType ) void sArrayElement::set( long val ) { - clear(); + clearData(); type = DataType::INT; intVal = val; } void sArrayElement::set( float val ) { - clear(); + clearData(); type = DataType::FLOAT; floatVal = val; } void sArrayElement::set( const char* val, int sLen /*= -1*/ ) { - clear(); + clearData(); type = DataType::STR; if (sLen == -1) sLen = strlen(val); if (sLen >= ARRAY_MAX_STRING) sLen = ARRAY_MAX_STRING - 1; // memory safety @@ -90,7 +90,7 @@ void sArrayElement::set( const char* val, int sLen /*= -1*/ ) void sArrayElement::unset() { - clear(); + clearData(); type = DataType::NONE; len = intVal = 0; } @@ -99,11 +99,11 @@ DWORD sArrayElement::getHashStatic(DWORD value, DataType type) { switch (type) { case DataType::STR: { const char* str = (const char*)value; - DWORD res = 0; - for (int i = 0; str[i] != '\0'; i++) { - res = ((res << 5) + res) + str[i]; + DWORD hash = 0; + while (*str) { + hash = ((hash << 5) + hash) + *str++; } - return res; + return hash; } case DataType::INT: case DataType::FLOAT: @@ -150,7 +150,14 @@ void sArrayVar::clearRange( int from, int to /*= -1*/ ) if (to == -1) to = val.size(); std::vector::iterator it, itTo = val.begin() + to; for (it = val.begin() + from; it < itTo; ++it) { - it->clear(); + it->clearData(); + } +} + +void sArrayVar::clearAll() +{ + for (std::vector::iterator it = val.begin(); it != val.end(); ++it) { + it->clearData(); } } @@ -179,6 +186,7 @@ bool LoadArrayElement(sArrayElement* el, HANDLE h) el->strVal = nullptr; } else { ReadFile(h, &el->intVal, 4, &unused, 0); + el->len = 0; } return (el->len) ? (unused != el->len) : (unused != 4); } @@ -202,8 +210,8 @@ static long LoadArraysOld(HANDLE h) { var.types = new DWORD[var.len]; var.data = new char[var.len * var.datalen]; - ReadFile(h, var.types, (4 * var.len), &unused, 0); - ReadFile(h, var.data, (var.len * var.datalen), &unused, 0); + ReadFile(h, &var.types, (4 * var.len), &unused, 0); + ReadFile(h, &var.data, (var.len * var.datalen), &unused, 0); varN.flags = 0; varN.val.resize(var.len); @@ -250,7 +258,6 @@ long LoadArrays(HANDLE h) { arrayVar.key.intVal = static_cast(arrayVar.key.type); arrayVar.key.type = DataType::INT; } - ReadFile(h, &arrayVar.flags, 4, &unused, 0); ReadFile(h, &elCount, 4, &unused, 0); // actual number of elements: keys+values if (unused != 4) return -1; @@ -268,6 +275,7 @@ long LoadArrays(HANDLE h) { arrays.insert(array_pair(nextArrayID, arrayVar)); savedArrays[arrayVar.key] = nextArrayID++; + arrayVar.keyHash.clear(); } return 0; @@ -277,29 +285,29 @@ void SaveArrays(HANDLE h) { DWORD elCount, unused, count = 0; WriteFile(h, &count, 4, &unused, 0); // this is for backward compatibility with 3.3- - array_itr arIt; + array_itr arrayIt; ArrayKeysMap::iterator it = savedArrays.begin(); while (it != savedArrays.end()) { - arIt = arrays.find(it->second); - if (arIt == arrays.end()) { - savedArrays.erase(it++); + arrayIt = arrays.find(it->second); // check the existence of an array + if (arrayIt == arrays.end()) { + it = savedArrays.erase(it); // array is not found, delete it [fix to C++11 https://en.cppreference.com/w/cpp/container/unordered_map/erase] } else { ++count; ++it; } } - WriteFile(h, &count, 4, &unused, 0); - + WriteFile(h, &count, 4, &unused, 0); // count saved arrays + if (count == 0) return; for (it = savedArrays.begin(); it != savedArrays.end(); ++it) { - arIt = arrays.find(it->second); - if (arIt != arrays.end()) { + arrayIt = arrays.find(it->second); + if (arrayIt != arrays.end()) { sArrayVar &var = arrays[it->second]; - SaveArrayElement(&var.key, h); + SaveArrayElement(&var.key, h); // type, key/length of key and string of key WriteFile(h, &var.flags, 4, &unused, 0); elCount = var.val.size(); - WriteFile(h, &elCount, 4, &unused, 0); - for (std::vector::iterator it = var.val.begin(); it != var.val.end(); ++it) { - SaveArrayElement(&(*it), h); + WriteFile(h, &elCount, 4, &unused, 0); // number of elements in saved array + for (std::vector::iterator vIt = var.val.begin(); vIt != var.val.end(); ++vIt) { + SaveArrayElement(&(*vIt), h); } } } @@ -371,7 +379,7 @@ void DESetArray(int id, const DWORD* types, const char* data) { TODO: move somewhere else */ -DWORD _stdcall CreateArray(int len, DWORD flags) { +DWORD CreateArray(int len, DWORD flags) { sArrayVar var; var.flags = (flags & 0xFFFFFFFE); // reset 1 bit if (len < 0) { @@ -395,17 +403,17 @@ DWORD _stdcall CreateArray(int len, DWORD flags) { return nextArrayID++; } -DWORD _stdcall TempArray(DWORD len, DWORD flags) { +DWORD TempArray(DWORD len, DWORD flags) { DWORD id = CreateArray(len, flags); tempArrays.insert(id); return id; } -void _stdcall FreeArray(DWORD id) { +void FreeArray(DWORD id) { array_itr it = arrays.find(id); if (it != arrays.end()) { - savedArrays.erase(it->second.key); - it->second.clear(); + if (it->second.key.intVal) savedArrays.erase(it->second.key); + it->second.clearArrayVar(); arrays.erase(id); } } @@ -419,7 +427,7 @@ void DeleteAllTempArrays() { } } -ScriptValue _stdcall GetArrayKey(DWORD id, int index) { +ScriptValue GetArrayKey(DWORD id, int index) { if (arrays.find(id) == arrays.end() || index < -1 || index > arrays[id].size()) { return ScriptValue(0); } @@ -446,7 +454,7 @@ ScriptValue _stdcall GetArrayKey(DWORD id, int index) { } } -ScriptValue _stdcall GetArray(DWORD id, const ScriptValue& key) { +ScriptValue GetArray(DWORD id, const ScriptValue& key) { if (arrays.find(id) == arrays.end()) { return 0; } @@ -478,7 +486,7 @@ ScriptValue _stdcall GetArray(DWORD id, const ScriptValue& key) { return ScriptValue(0); } -void _stdcall SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset) { +void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset) { if (arrays.find(id) == arrays.end()) { return; } @@ -529,7 +537,7 @@ void _stdcall SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, } } -int _stdcall LenArray(DWORD id) { +int LenArray(DWORD id) { if (arrays.find(id) == arrays.end()) return -1; return arrays[id].size(); } @@ -578,7 +586,7 @@ static void MapSort(sArrayVar& arr, int type) { arr.val.clear(); arr.keyHash.clear(); for (size_t i = 0; i < vmap.size(); ++i) { - auto el = arr.val.size(); + size_t el = arr.val.size(); if (sortByValue) { arr.val.emplace_back(vmap[i].second); // map value > key arr.val.emplace_back(vmap[i].first); // map key > value @@ -590,12 +598,16 @@ static void MapSort(sArrayVar& arr, int type) { } } -long _stdcall ResizeArray(DWORD id, int newlen) { - if (newlen == -1 || arrays.find(id) == arrays.end() || arrays[id].size() == newlen) return 0; +long ResizeArray(DWORD id, int newlen) { + if (newlen == -1 || arrays.find(id) == arrays.end()) return 0; + sArrayVar &arr = arrays[id]; + int arrSize = arr.size(); + if (arrSize == newlen) return 0; + if (arr.isAssoc()) { // only allow to reduce number of elements (adding range of elements is meaningless for maps) - if (newlen >= 0 && newlen < arrays[id].size()) { + if (newlen >= 0 && newlen < arrSize) { ArrayKeysMap::iterator itHash; std::vector::iterator itVal; int actualLen = newlen * 2; @@ -603,7 +615,10 @@ long _stdcall ResizeArray(DWORD id, int newlen) { if ((itHash = arr.keyHash.find(*itVal)) != arr.keyHash.end()) arr.keyHash.erase(itHash); } - arr.clearRange(actualLen); + if (actualLen == 0) + arr.clearAll(); + else + arr.clearRange(actualLen); arr.val.resize(actualLen); } else if (newlen < 0) { if (newlen < (ARRAY_ACTION_SHUFFLE - 2)) return -1; @@ -612,10 +627,12 @@ long _stdcall ResizeArray(DWORD id, int newlen) { return 0; } if (newlen >= 0) { // actual resize - if (newlen > ARRAY_MAX_SIZE) // safety - newlen = ARRAY_MAX_SIZE; - if (newlen < arr.size()) - arr.clearRange(newlen); + if (newlen == 0) { + arr.clearAll(); + } else { + if (newlen > ARRAY_MAX_SIZE) newlen = ARRAY_MAX_SIZE; // safety + if (newlen < arrSize) arr.clearRange(newlen); + } arr.val.resize(newlen); } else { // special functions for lists... if (newlen < ARRAY_ACTION_SHUFFLE) return -1; @@ -624,11 +641,11 @@ long _stdcall ResizeArray(DWORD id, int newlen) { return 0; } -void _stdcall FixArray(DWORD id) { +void FixArray(DWORD id) { tempArrays.erase(id); } -ScriptValue _stdcall ScanArray(DWORD id, const ScriptValue& val) { +ScriptValue ScanArray(DWORD id, const ScriptValue& val) { if (arrays.find(id) == arrays.end()) { return ScriptValue(-1); } @@ -652,7 +669,7 @@ ScriptValue _stdcall ScanArray(DWORD id, const ScriptValue& val) { return ScriptValue(-1); } -DWORD _stdcall LoadArray(const ScriptValue& key) { +DWORD LoadArray(const ScriptValue& key) { if (!key.isInt() || key.asInt() != 0) { // returns arrayId by it's key (ignoring int(0) because it is used to "unsave" array) sArrayElement keyEl = sArrayElement(key.rawValue(), key.type()); @@ -678,22 +695,29 @@ DWORD _stdcall LoadArray(const ScriptValue& key) { return 0; // not found } -void _stdcall SaveArray(const ScriptValue& key, DWORD id) { - array_itr it = arrays.find(id), it2; - if (it != arrays.end()) { +void SaveArray(const ScriptValue& key, DWORD id) { + array_itr it, itArray = arrays.find(id); // arrayId => arrayVar + if (itArray != arrays.end()) { if (!key.isInt() || key.asInt() != 0) { // make array permanent - FixArray(it->first); + FixArray(itArray->first); // if another array is saved under the same key, clear it - ArrayKeysMap::iterator sIt = savedArrays.find(sArrayElement(key.rawValue(), key.type())); - if (sIt != savedArrays.end() && sIt->second != id && (it2 = arrays.find(sIt->second)) != arrays.end()) { - it2->second.key.unset(); + ArrayKeysMap::iterator savedIt = savedArrays.find(sArrayElement(key.rawValue(), key.type())); + if (savedIt != savedArrays.end()) { + if (savedIt->second == id) return; // exit, array is already saveable + // arrays have different ID, search the ID of the "saved" array in collection of arrays + if ((it = arrays.find(savedIt->second)) != arrays.end()) { + // array exists, delete key value + savedArrays.erase(savedIt); /* added for fix */ + it->second.key.unset(); + } } - it->second.key.setByType(key.rawValue(), key.type()); - savedArrays[it->second.key] = id; - } else { // int(0) is used to "unsave" array without destroying it - int num = savedArrays.erase(it->second.key); - it->second.key.unset(); + // make array "saved" + itArray->second.key.setByType(key.rawValue(), key.type()); + savedArrays.emplace(itArray->second.key, id); // savedArrays[itArray->second.key] = id; + } else { // key of int(0) is used to "unsave" array without destroying it + savedArrays.erase(itArray->second.key); + itArray->second.key.unset(); } } } @@ -708,7 +732,7 @@ void _stdcall SaveArray(const ScriptValue& key, DWORD id) { Should always return 0! */ -DWORD _stdcall StackArray(const ScriptValue& key, const ScriptValue& val) { +long StackArray(const ScriptValue& key, const ScriptValue& val) { DWORD id = stackArrayId; if (id == 0 || arrays.find(id) == arrays.end()) { return 0; diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index 0d2ccfd4..d8d4d1c4 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -40,7 +40,7 @@ public: sArrayElement(const long&); // free string resource from memory, has to be called manually when deleting element - void clear(); + void clearData(); // set* methods will actually COPY strings, use this when acquiring data from the scripting engine void setByType(DWORD val, DataType dataType); @@ -107,15 +107,16 @@ public: ArrayKeysMap keyHash; // key element => element index, for faster lookup std::vector val; // list of values or key=>value pairs (even - keys, odd - values) + sArrayVar() : flags(0), key() {} + bool isAssoc() const { return (flags & ARRAYFLAG_ASSOC); } // logical array size (number of elements for normal arrays; number of key=>value pairs for associative) int size() const { - return isAssoc() - ? val.size() / 2 - : val.size(); + size_t sz = val.size(); + return isAssoc() ? sz / 2 : sz; } // usefull when filling array from within sfall code (normal lists only) @@ -125,15 +126,14 @@ public: } } - sArrayVar() : flags(0), key() {} - // free memory used by strings - void clear() { - clearRange(0); - key.clear(); + void clearArrayVar() { + clearAll(); + key.clearData(); } void clearRange(int from, int to = -1); + void clearAll(); }; // arrays map: arrayId => arrayVar @@ -160,13 +160,13 @@ void DEGetArray(int id, DWORD* types, char* data); void DESetArray(int id, const DWORD* types, const char* data); // creates new normal (persistent) array. len == -1 specifies associative array (map) -DWORD _stdcall CreateArray(int len, DWORD flags); +DWORD CreateArray(int len, DWORD flags); // same as CreateArray, but creates temporary array instead (will die at the end of the frame) -DWORD _stdcall TempArray(DWORD len, DWORD flags); +DWORD TempArray(DWORD len, DWORD flags); // destroys array -void _stdcall FreeArray(DWORD id); +void FreeArray(DWORD id); // destroy all temp arrays void DeleteAllTempArrays(); @@ -174,34 +174,34 @@ void DeleteAllTempArrays(); /* op_get_array_key can be used to iterate over all keys in associative array */ -ScriptValue _stdcall GetArrayKey(DWORD id, int index); +ScriptValue GetArrayKey(DWORD id, int index); // get array element by index (list) or key (map) -ScriptValue _stdcall GetArray(DWORD id, const ScriptValue& key); +ScriptValue GetArray(DWORD id, const ScriptValue& key); // set array element by index or key -void _stdcall SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset); +void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset); // number of elements in list or pairs in map -int _stdcall LenArray(DWORD id); +int LenArray(DWORD id); // change array size (only works with list) -long _stdcall ResizeArray(DWORD id, int newlen); +long ResizeArray(DWORD id, int newlen); // make temporary array persistent -void _stdcall FixArray(DWORD id); +void FixArray(DWORD id); // searches for a given element in array and returns it's index (for list) or key (for map) or int(-1) if not found -ScriptValue _stdcall ScanArray(DWORD id, const ScriptValue& val); +ScriptValue ScanArray(DWORD id, const ScriptValue& val); // get saved array by it's key -DWORD _stdcall LoadArray(const ScriptValue& key); +DWORD LoadArray(const ScriptValue& key); // make array saved into the savegame with associated key -void _stdcall SaveArray(const ScriptValue& key, DWORD id); +void SaveArray(const ScriptValue& key, DWORD id); // special function that powers array expressions -DWORD _stdcall StackArray(const ScriptValue& key, const ScriptValue& val); +long StackArray(const ScriptValue& key, const ScriptValue& val); } } diff --git a/sfall/Modules/Scripting/Handlers/Arrays.cpp b/sfall/Modules/Scripting/Handlers/Arrays.cpp index 32133d58..21aa1a3f 100644 --- a/sfall/Modules/Scripting/Handlers/Arrays.cpp +++ b/sfall/Modules/Scripting/Handlers/Arrays.cpp @@ -30,13 +30,13 @@ namespace script { void sf_create_array(OpcodeContext& ctx) { - auto arrayId = CreateArray(ctx.arg(0).asInt(), ctx.arg(1).asInt()); + auto arrayId = CreateArray(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); ctx.setReturn(arrayId, DataType::INT); } void sf_set_array(OpcodeContext& ctx) { SetArray( - ctx.arg(0).asInt(), + ctx.arg(0).rawValue(), ctx.arg(1), ctx.arg(2), true @@ -65,55 +65,55 @@ void sf_get_array(OpcodeContext& ctx) { } void sf_free_array(OpcodeContext& ctx) { - FreeArray(ctx.arg(0).asInt()); + FreeArray(ctx.arg(0).rawValue()); } void sf_len_array(OpcodeContext& ctx) { ctx.setReturn( - LenArray(ctx.arg(0).asInt()) + LenArray(ctx.arg(0).rawValue()) ); } void sf_resize_array(OpcodeContext& ctx) { - if (ResizeArray(ctx.arg(0).asInt(), ctx.arg(1).asInt())) { + if (ResizeArray(ctx.arg(0).rawValue(), ctx.arg(1).rawValue())) { ctx.printOpcodeError("%s() - array sorting error.", ctx.getOpcodeName()); } } void sf_temp_array(OpcodeContext& ctx) { - auto arrayId = TempArray(ctx.arg(0).asInt(), ctx.arg(1).asInt()); + auto arrayId = TempArray(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); ctx.setReturn(arrayId, DataType::INT); } void sf_fix_array(OpcodeContext& ctx) { - FixArray(ctx.arg(0).asInt()); + FixArray(ctx.arg(0).rawValue()); } void sf_scan_array(OpcodeContext& ctx) { ctx.setReturn( - ScanArray(ctx.arg(0).asInt(), ctx.arg(1)) + ScanArray(ctx.arg(0).rawValue(), ctx.arg(1)) ); } void sf_save_array(OpcodeContext& ctx) { - SaveArray(ctx.arg(0), ctx.arg(1).asInt()); + SaveArray(ctx.arg(0), ctx.arg(1).rawValue()); } void sf_load_array(OpcodeContext& ctx) { ctx.setReturn( - static_cast(LoadArray(ctx.arg(0))) + LoadArray(ctx.arg(0)) ); } void sf_get_array_key(OpcodeContext& ctx) { ctx.setReturn( - GetArrayKey(ctx.arg(0).asInt(), ctx.arg(1).asInt()) + GetArrayKey(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()) ); } void sf_stack_array(OpcodeContext& ctx) { ctx.setReturn( - static_cast(StackArray(ctx.arg(0), ctx.arg(1))) + StackArray(ctx.arg(0), ctx.arg(1)) ); } diff --git a/sfall/Modules/Scripting/Handlers/Core.cpp b/sfall/Modules/Scripting/Handlers/Core.cpp index b086ab59..ea0a17e1 100644 --- a/sfall/Modules/Scripting/Handlers/Core.cpp +++ b/sfall/Modules/Scripting/Handlers/Core.cpp @@ -35,13 +35,11 @@ namespace script void __declspec(naked) op_set_global_script_repeat() { __asm { push ecx; - push edx; mov ecx, eax; _GET_ARG_INT(end); mov edx, eax; // frames call SetGlobalScriptRepeat; // ecx - script end: - pop edx; pop ecx; retn; } @@ -50,13 +48,11 @@ end: void __declspec(naked) op_set_global_script_type() { __asm { push ecx; - push edx; mov ecx, eax; _GET_ARG_INT(end); mov edx, eax; // type call SetGlobalScriptType; // ecx - script end: - pop edx; pop ecx; retn; } @@ -65,10 +61,8 @@ end: void __declspec(naked) op_available_global_script_types() { __asm { push ecx; - push edx; mov edx, availableGlobalScriptTypes; _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } @@ -114,79 +108,37 @@ void sf_get_sfall_global_float(OpcodeContext& ctx) { void __declspec(naked) op_get_sfall_arg() { __asm { push ecx; - push edx; push eax; call GetHSArg; mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } } -static DWORD _stdcall GetSfallArgs() { +void sf_get_sfall_args(OpcodeContext& ctx) { DWORD argCount = GetHSArgCount(); DWORD id = TempArray(argCount, 0); DWORD* args = GetHSArgs(); for (DWORD i = 0; i < argCount; i++) { arrays[id].val[i].set(*(long*)&args[i]); } - return id; + ctx.setReturn(id); } -void __declspec(naked) op_get_sfall_args() { // rewrite to c++ - __asm { - push ecx; - push edx; - push eax; - call GetSfallArgs; - mov edx, eax; - pop eax; - _RET_VAL_INT(ecx); - pop edx; - pop ecx; - retn; - } -} - -void __declspec(naked) op_set_sfall_arg() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - mov edi, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - mov edx, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - mov esi, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - cmp di, VAR_TYPE_INT; - jnz end; - cmp si, VAR_TYPE_INT; - jnz end; - push edx; - push eax; - call SetHSArg; -end: - popad; - retn; - } +void sf_set_sfall_arg(OpcodeContext& ctx) { + SetHSArg(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); } void __declspec(naked) op_set_sfall_return() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call SetHSReturn; end: - pop edx; pop ecx; retn; } @@ -194,26 +146,22 @@ end: void __declspec(naked) op_init_hook() { __asm { - push edx; push ecx; mov edx, initingHookScripts; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } -void __declspec(naked) op_set_self() { // rewrite to c++ +void __declspec(naked) op_set_self() { __asm { push ecx; - push edx; mov ecx, eax; _GET_ARG_INT(end); mov edx, eax; // object call SetSelfObject; // ecx - script end: - pop edx; pop ecx; retn; } diff --git a/sfall/Modules/Scripting/Handlers/Core.h b/sfall/Modules/Scripting/Handlers/Core.h index eb1ead6e..f3d76a91 100644 --- a/sfall/Modules/Scripting/Handlers/Core.h +++ b/sfall/Modules/Scripting/Handlers/Core.h @@ -41,9 +41,9 @@ void sf_get_sfall_global_float(OpcodeContext&); void __declspec() op_get_sfall_arg(); -void __declspec() op_get_sfall_args(); +void sf_get_sfall_args(OpcodeContext&); -void __declspec() op_set_sfall_arg(); +void sf_set_sfall_arg(OpcodeContext&); void __declspec() op_set_sfall_return(); diff --git a/sfall/Modules/Scripting/Handlers/Graphics.cpp b/sfall/Modules/Scripting/Handlers/Graphics.cpp index 39d7d2e5..18b4c9d5 100644 --- a/sfall/Modules/Scripting/Handlers/Graphics.cpp +++ b/sfall/Modules/Scripting/Handlers/Graphics.cpp @@ -32,14 +32,12 @@ namespace script void __declspec(naked) op_graphics_funcs_available() { __asm { - push edx; push ecx; cmp Graphics::mode, 3; seta dl; and edx, 0xFF; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -86,12 +84,10 @@ result: void __declspec(naked) op_free_shader() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call FreeShader; end: - pop edx; pop ecx; retn; } @@ -100,12 +96,10 @@ end: void __declspec(naked) op_activate_shader() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call ActivateShader; end: - pop edx; pop ecx; retn; } @@ -114,12 +108,10 @@ end: void __declspec(naked) op_deactivate_shader() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call DeactivateShader; end: - pop edx; pop ecx; retn; } @@ -414,13 +406,11 @@ end: void __declspec(naked) op_get_shader_version() { __asm { push ecx; - push edx; push eax; call GetShaderVersion; mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } @@ -464,12 +454,10 @@ end: void __declspec(naked) op_force_graphics_refresh() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call ForceGraphicsRefresh; end: - pop edx; pop ecx; retn; } diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 76fcf180..8af61eaa 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -23,6 +23,8 @@ #include "..\..\ScriptExtender.h" #include "..\OpcodeContext.h" +#include "..\..\HookScripts\InventoryHs.h" + #include "Interface.h" namespace sfall @@ -33,10 +35,8 @@ namespace script void __declspec(naked) op_input_funcs_available() { __asm { push ecx; - push edx; mov edx, 1; // They're always available from 2.9 on _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } @@ -49,7 +49,6 @@ void sf_key_pressed(OpcodeContext& ctx) { void __declspec(naked) op_tap_key() { __asm { push ecx; - push edx; _GET_ARG_INT(end); test eax, eax; jl end; @@ -58,7 +57,6 @@ void __declspec(naked) op_tap_key() { push eax; call TapKey; end: - pop edx; pop ecx; retn; } @@ -66,26 +64,22 @@ end: void __declspec(naked) op_get_mouse_x() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_mouse_x_]; add edx, ds:[FO_VAR_mouse_hotx]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } void __declspec(naked) op_get_mouse_y() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_mouse_y_]; add edx, ds:[FO_VAR_mouse_hoty]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -101,40 +95,34 @@ void sf_get_mouse_buttons(OpcodeContext& ctx) { void __declspec(naked) op_get_window_under_mouse() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_last_button_winID]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } void __declspec(naked) op_get_screen_width() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_scr_size + 8]; // _scr_size.offx sub edx, ds:[FO_VAR_scr_size]; // _scr_size.x inc edx; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } void __declspec(naked) op_get_screen_height() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_scr_size + 12]; // _scr_size.offy sub edx, ds:[FO_VAR_scr_size + 4]; // _scr_size.y inc edx; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -163,24 +151,20 @@ void sf_create_message_window(OpcodeContext &ctx) { void __declspec(naked) op_get_viewport_x() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_wmWorldOffsetX]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } void __declspec(naked) op_get_viewport_y() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_wmWorldOffsetY]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -188,11 +172,9 @@ void __declspec(naked) op_get_viewport_y() { void __declspec(naked) op_set_viewport_x() { __asm { push ecx; - push edx; _GET_ARG_INT(end); mov ds:[FO_VAR_wmWorldOffsetX], eax; end: - pop edx; pop ecx; retn; } @@ -201,11 +183,9 @@ end: void __declspec(naked) op_set_viewport_y() { __asm { push ecx; - push edx; _GET_ARG_INT(end); mov ds:[FO_VAR_wmWorldOffsetY], eax; end: - pop edx; pop ecx; retn; } @@ -220,8 +200,8 @@ void sf_add_iface_tag(OpcodeContext &ctx) { void sf_show_iface_tag(OpcodeContext &ctx) { int tag = ctx.arg(0).asInt(); if (tag == 3 || tag == 4) { - _asm mov eax, tag; - _asm call fo::funcoffs::pc_flag_on_; + __asm mov eax, tag; + __asm call fo::funcoffs::pc_flag_on_; } else { BarBoxes::AddBox(tag); } @@ -230,8 +210,8 @@ void sf_show_iface_tag(OpcodeContext &ctx) { void sf_hide_iface_tag(OpcodeContext &ctx) { int tag = ctx.arg(0).asInt(); if (tag == 3 || tag == 4) { - _asm mov eax, tag; - _asm call fo::funcoffs::pc_flag_off_; + __asm mov eax, tag; + __asm call fo::funcoffs::pc_flag_off_; } else { BarBoxes::RemoveBox(tag); } @@ -292,7 +272,7 @@ void sf_get_cursor_mode(OpcodeContext& ctx) { int cursorMode; __asm { call fo::funcoffs::gmouse_3d_get_mode_; - mov cursorMode, eax; + mov cursorMode, eax; } ctx.setReturn(cursorMode); } @@ -321,19 +301,23 @@ void sf_set_iface_tag_text(OpcodeContext& ctx) { void sf_inventory_redraw(OpcodeContext& ctx) { int mode; - DWORD loopFlag = GetLoopFlags(); - if (loopFlag & INVENTORY) { - mode = 0; - } else if (loopFlag & INTFACEUSE) { - mode = 1; - } else if (loopFlag & INTFACELOOT) { - mode = 2; - } else if (loopFlag & BARTER) { - mode = 3; - } else { - return; + DWORD loopFlag = GetLoopFlags() & (INVENTORY | INTFACEUSE | INTFACELOOT | BARTER); + switch (loopFlag) { + case INVENTORY: + mode = 0; + break; + case INTFACEUSE: + mode = 1; + break; + case INTFACELOOT: + mode = 2; + break; + case BARTER: + mode = 3; + break; + default: + return; } - if (!ctx.arg(0).asBool()) { fo::var::stack_offset[fo::var::curr_stack] = 0; fo::func::display_inventory(0, -1, mode); @@ -450,5 +434,65 @@ void sf_draw_image_scaled(OpcodeContext& ctx) { DrawImage(ctx, true); } +void sf_unwield_slot(OpcodeContext& ctx) { + fo::InvenType slot = static_cast(ctx.arg(1).rawValue()); + if (slot < fo::INVEN_TYPE_WORN || slot > fo::INVEN_TYPE_LEFT_HAND) { + ctx.printOpcodeError("%s() - incorrect slot number.", ctx.getMetaruleName()); + return; + } + fo::GameObject* critter = ctx.arg(0).asObject(); + if (critter->Type() != fo::ObjType::OBJ_TYPE_CRITTER) { + ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getMetaruleName()); + return; + } + bool isDude = (critter == fo::var::obj_dude); + bool update = false; + if (slot && (GetLoopFlags() && (INVENTORY | INTFACEUSE | INTFACELOOT | BARTER)) == false) { + if (fo::func::inven_unwield(critter, (slot == fo::INVEN_TYPE_LEFT_HAND) ? fo::Left : fo::Right) == 0) { + update = isDude; + } + } else { + // force unwield for opened inventory + bool forceAdd = false; + fo::GameObject* item = nullptr; + if (slot != fo::INVEN_TYPE_WORN) { + if (!isDude) return; + long* itemRef = nullptr; + if (slot == fo::INVEN_TYPE_LEFT_HAND) { + item = fo::var::i_lhand; + itemRef = (long*)FO_VAR_i_lhand; + } else { + item = fo::var::i_rhand; + itemRef = (long*)FO_VAR_i_rhand; + } + if (item) { + if (!CorrectFidForRemovedItem_wHook(critter, item, (slot == fo::INVEN_TYPE_LEFT_HAND) ? fo::ObjectFlag::Left_Hand : fo::ObjectFlag::Right_Hand)) { + return; + } + *itemRef = 0; + forceAdd = true; + update = true; + } + } else { + if (isDude) item = fo::var::i_worn; + if (!item) { + item = fo::func::inven_worn(critter); + } else { + fo::var::i_worn = nullptr; + forceAdd = true; + } + if (item) { + if (!CorrectFidForRemovedItem_wHook(critter, item, fo::ObjectFlag::Worn)) { + if (forceAdd) fo::var::i_worn = item; + return; + } + if (isDude) fo::func::intface_update_ac(0); + } + } + if (forceAdd) fo::func::item_add_force(critter, item, 1); + } + if (update) fo::func::intface_update_items(0, -1, -1); +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index f6dcaa02..f1bfd5ac 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -103,5 +103,7 @@ void sf_draw_image(OpcodeContext&); void sf_draw_image_scaled(OpcodeContext&); +void sf_unwield_slot(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 618e1cc5..b65209b1 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -61,6 +61,7 @@ static MetaruleTableType metaruleTable; static const SfallMetarule metarules[] = { {"add_extra_msg_file", sf_add_extra_msg_file, 1, 2, {ARG_STRING, ARG_INT}}, {"add_iface_tag", sf_add_iface_tag, 0, 0}, + {"add_trait", sf_add_trait, 1, 1, {ARG_INT}}, {"art_cache_clear", sf_art_cache_flush, 0, 0}, {"attack_is_aimed", sf_attack_is_aimed, 0, 0}, {"car_gas_amount", sf_car_gas_amount, 0, 0}, @@ -79,6 +80,7 @@ static const SfallMetarule metarules[] = { {"get_flags", sf_get_flags, 1, 1, {ARG_OBJECT}}, {"get_ini_section", sf_get_ini_section, 2, 2, {ARG_STRING, ARG_STRING}}, {"get_ini_sections", sf_get_ini_sections, 1, 1, {ARG_STRING}}, + {"get_inven_ap_cost", sf_get_inven_ap_cost, 0, 0}, {"get_map_enter_position", sf_get_map_enter_position, 0, 0}, {"get_metarule_table", sf_get_metarule_table, 0, 0}, {"get_object_ai_data", sf_get_object_ai_data, 2, 2, {ARG_OBJECT, ARG_INT}}, @@ -122,6 +124,7 @@ static const SfallMetarule metarules[] = { {"spatial_radius", sf_spatial_radius, 1, 1, {ARG_OBJECT}}, {"tile_refresh_display", sf_tile_refresh_display, 0, 0}, {"unjam_lock", sf_unjam_lock, 1, 1, {ARG_OBJECT}}, + {"unwield_slot", sf_unwield_slot, 2, 2, {ARG_OBJECT, ARG_INT}}, #ifndef NDEBUG {"validate_test", sf_test, 2, 5, {ARG_INT, ARG_NUMBER, ARG_STRING, ARG_OBJECT, ARG_ANY}}, #endif diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index fedfa24c..ce06d7fc 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -28,12 +28,10 @@ #include "..\..\KillCounter.h" //#include "..\..\MiscPatches.h" #include "..\..\Movies.h" -#include "..\..\Objects.h" #include "..\..\PartyControl.h" #include "..\..\PlayerModel.h" #include "..\..\ScriptExtender.h" #include "..\..\Stats.h" -//#include "..\..\Worldmap.h" #include "..\Arrays.h" #include "..\OpcodeContext.h" @@ -291,8 +289,6 @@ void sf_set_object_knockback(OpcodeContext& ctx) { case 0x197: mode = 2; break; - default: - break; } fo::GameObject* object = ctx.arg(0).asObject(); if (mode) { @@ -318,8 +314,6 @@ void sf_remove_object_knockback(OpcodeContext& ctx) { case 0x19a: mode = 2; break; - default: - break; } KnockbackRemoveMod(ctx.arg(0).asObject(), mode); } @@ -392,12 +386,10 @@ end: void __declspec(naked) op_active_hand() { __asm { - push edx; push ecx; mov edx, dword ptr ds:[FO_VAR_itemCurrentItem]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -411,12 +403,10 @@ void __declspec(naked) op_toggle_active_hand() { void __declspec(naked) op_eax_available() { __asm { - push edx; push ecx; xor edx, edx _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -572,10 +562,10 @@ static DWORD GetIniSetting(const char* str, bool isString) { } if (isString) { IniStrBuffer[0] = 0; - GetPrivateProfileStringA(section, key, "", IniStrBuffer, 256, file); + iniGetString(section, key, "", IniStrBuffer, 256, file); return (DWORD)&IniStrBuffer[0]; } else { - return GetPrivateProfileIntA(section, key, -1, file); + return iniGetInt(section, key, -1, file); } } @@ -591,13 +581,11 @@ void sf_get_ini_string(OpcodeContext& ctx) { void __declspec(naked) op_get_uptime() { __asm { push ecx; - push edx; push eax; call GetTickCount; mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } @@ -606,11 +594,9 @@ void __declspec(naked) op_get_uptime() { void __declspec(naked) op_set_car_current_town() { __asm { push ecx; - push edx; _GET_ARG_INT(end); mov ds:[FO_VAR_carCurrentArea], eax; end: - pop edx; pop ecx; retn; } @@ -619,13 +605,11 @@ end: void __declspec(naked) op_set_hp_per_level_mod() { // rewrite to c++ __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; // allowed -/+127 push 0x4AFBC1; call SafeWrite8; end: - pop edx; pop ecx; retn; } @@ -694,7 +678,7 @@ end: } } -static char* valueOutRange = "%s() - argument values out of range."; +static const char* valueOutRange = "%s() - argument values out of range."; void sf_set_critical_table(OpcodeContext& ctx) { DWORD critter = ctx.arg(0).asInt(), @@ -738,11 +722,9 @@ void sf_reset_critical_table(OpcodeContext& ctx) { void __declspec(naked) op_set_unspent_ap_bonus() { __asm { push ecx; - push edx; _GET_ARG_INT(end); - mov standardApAcBonus, eax; + mov Stats::standardApAcBonus, eax; end: - pop edx; pop ecx; retn; } @@ -750,12 +732,10 @@ end: void __declspec(naked) op_get_unspent_ap_bonus() { __asm { - push edx; push ecx; - mov edx, standardApAcBonus; + mov edx, Stats::standardApAcBonus; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -763,11 +743,9 @@ void __declspec(naked) op_get_unspent_ap_bonus() { void __declspec(naked) op_set_unspent_ap_perk_bonus() { __asm { push ecx; - push edx; _GET_ARG_INT(end); - mov extraApAcBonus, eax; + mov Stats::extraApAcBonus, eax; end: - pop edx; pop ecx; retn; } @@ -775,12 +753,10 @@ end: void __declspec(naked) op_get_unspent_ap_perk_bonus() { __asm { - push edx; push ecx; - mov edx, extraApAcBonus; + mov edx, Stats::extraApAcBonus; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -834,42 +810,13 @@ void __declspec(naked) op_nb_create_char() { } } -static const char* failedLoad = "%s() - failed to load a prototype id: %d"; -static bool protoMaxLimitPatch = false; - -void sf_get_proto_data(OpcodeContext& ctx) { - fo::Proto* protoPtr; - int pid = ctx.arg(0).rawValue(); - int result = fo::func::proto_ptr(pid, &protoPtr); - if (result != -1) { - result = *(long*)((BYTE*)protoPtr + ctx.arg(1).rawValue()); - } else { - ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); - } - ctx.setReturn(result); -} - -void sf_set_proto_data(OpcodeContext& ctx) { - int pid = ctx.arg(0).rawValue(); - if (Stats::SetProtoData(pid, ctx.arg(1).rawValue(), ctx.arg(2).rawValue()) != -1) { - if (!protoMaxLimitPatch) { - Objects::LoadProtoAutoMaxLimit(); - protoMaxLimitPatch = true; - } - } else { - ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); - } -} - void __declspec(naked) op_hero_select_win() { // for opening the appearance selection window __asm { push ecx; - push edx; _GET_ARG_INT(fail); push eax; call HeroSelectWindow; fail: - pop edx; pop ecx; retn; } @@ -878,12 +825,10 @@ fail: void __declspec(naked) op_set_hero_style() { // for setting the hero style/appearance takes an 1 int __asm { push ecx; - push edx; _GET_ARG_INT(fail); push eax; call SetHeroStyle; fail: - pop edx; pop ecx; retn; } @@ -892,12 +837,10 @@ fail: void __declspec(naked) op_set_hero_race() { // for setting the hero race takes an 1 int __asm { push ecx; - push edx; _GET_ARG_INT(fail); push eax; call SetHeroRace; fail: - pop edx; pop ecx; retn; } @@ -905,12 +848,10 @@ fail: void __declspec(naked) op_get_light_level() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_ambient_light]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -918,9 +859,7 @@ void __declspec(naked) op_get_light_level() { void __declspec(naked) op_refresh_pc_art() { __asm { push ecx; - push edx; call RefreshPCArt; - pop edx; pop ecx; retn; } @@ -974,12 +913,10 @@ end: void __declspec(naked) op_stop_sfall_sound() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call StopSfallSound; end: - pop edx; pop ecx; retn; } @@ -1021,12 +958,10 @@ end: void __declspec(naked) op_modified_ini() { __asm { - push edx; push ecx; mov edx, modifiedIni; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -1034,12 +969,10 @@ void __declspec(naked) op_modified_ini() { void __declspec(naked) op_force_aimed_shots() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call ForceAimedShots; end: - pop edx; pop ecx; retn; } @@ -1048,12 +981,10 @@ end: void __declspec(naked) op_disable_aimed_shots() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call DisableAimedShots; end: - pop edx; pop ecx; retn; } @@ -1062,7 +993,6 @@ end: void __declspec(naked) op_mark_movie_played() { __asm { push ecx; - push edx; _GET_ARG_INT(end); test eax, eax; jl end; @@ -1070,7 +1000,6 @@ void __declspec(naked) op_mark_movie_played() { jge end; mov byte ptr ds:[eax + FO_VAR_gmovie_played_list], 1; end: - pop edx; pop ecx; retn; } @@ -1133,12 +1062,10 @@ end: void __declspec(naked) op_block_combat() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call AIBlockCombat; end: - pop edx; pop ecx; retn; } @@ -1174,12 +1101,10 @@ void __declspec(naked) op_tile_under_cursor() { void __declspec(naked) op_gdialog_get_barter_mod() { __asm { - push edx; push ecx; mov edx, dword ptr ds:[FO_VAR_gdBarterMod]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -1187,17 +1112,19 @@ void __declspec(naked) op_gdialog_get_barter_mod() { void __declspec(naked) op_set_inven_ap_cost() { __asm { push ecx; - push edx; _GET_ARG_INT(end); - push eax; - call SetInvenApCost; + mov ecx, eax; + call Inventory::SetInvenApCost; end: - pop edx; pop ecx; retn; } } +void sf_get_inven_ap_cost(OpcodeContext& ctx) { + ctx.setReturn(Inventory::GetInvenApCost()); +} + void sf_attack_is_aimed(OpcodeContext& ctx) { DWORD isAimed, unused; ctx.setReturn(!fo::func::intface_get_attack(&unused, &isAimed) ? isAimed : 0); diff --git a/sfall/Modules/Scripting/Handlers/Misc.h b/sfall/Modules/Scripting/Handlers/Misc.h index 9cacd176..dfbf73a8 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.h +++ b/sfall/Modules/Scripting/Handlers/Misc.h @@ -99,10 +99,6 @@ void __declspec() op_set_palette(); //numbers subgame functions void __declspec() op_nb_create_char(); -void sf_get_proto_data(OpcodeContext&); - -void sf_set_proto_data(OpcodeContext&); - void __declspec() op_hero_select_win() ; void __declspec() op_set_hero_style(); @@ -141,6 +137,8 @@ void __declspec() op_gdialog_get_barter_mod(); void __declspec() op_set_inven_ap_cost(); +void sf_get_inven_ap_cost(OpcodeContext&); + void sf_attack_is_aimed(OpcodeContext&); void sf_sneak_success(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 86a37ff2..0af741c7 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -18,6 +18,7 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\Combat.h" +#include "..\..\CritterStats.h" #include "..\..\Drugs.h" #include "..\..\Explosions.h" #include "..\..\Inventory.h" @@ -434,14 +435,51 @@ void sf_get_loot_object(OpcodeContext& ctx) { ctx.setReturn((GetLoopFlags() & INTFACELOOT) ? LoadGameHook::LootTarget : 0); } +static const char* failedLoad = "%s() - failed to load a prototype ID: %d"; +static bool protoMaxLimitPatch = false; + +void sf_get_proto_data(OpcodeContext& ctx) { + fo::Proto* protoPtr; + int pid = ctx.arg(0).rawValue(); + int result = fo::func::proto_ptr(pid, &protoPtr); + if (result != -1) { + result = *(long*)((BYTE*)protoPtr + ctx.arg(1).rawValue()); + } else { + ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); + } + ctx.setReturn(result); +} + +void sf_set_proto_data(OpcodeContext& ctx) { + int pid = ctx.arg(0).rawValue(); + if (CritterStats::SetProtoData(pid, ctx.arg(1).rawValue(), ctx.arg(2).rawValue()) != -1) { + if (!protoMaxLimitPatch) { + Objects::LoadProtoAutoMaxLimit(); + protoMaxLimitPatch = true; + } + } else { + ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); + } +} + void sf_get_object_data(OpcodeContext& ctx) { - BYTE* object_ptr = (BYTE*)ctx.arg(0).asObject(); - ctx.setReturn(*(long*)(object_ptr + ctx.arg(1).asInt()), DataType::INT); + DWORD result = 0; + DWORD* object_ptr = (DWORD*)ctx.arg(0).rawValue(); + if (*(object_ptr - 1) != 0xFEEDFACE) { + ctx.printOpcodeError("%s() - invalid object pointer.", ctx.getMetaruleName()); + } else { + result = *(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()); + } + ctx.setReturn(result, DataType::INT); } void sf_set_object_data(OpcodeContext& ctx) { - BYTE* object_ptr = (BYTE*)ctx.arg(0).asObject(); - *(long*)(object_ptr + ctx.arg(1).asInt()) = ctx.arg(2).asInt(); + DWORD* object_ptr = (DWORD*)ctx.arg(0).rawValue(); + if (*(object_ptr - 1) != 0xFEEDFACE) { + ctx.printOpcodeError("%s() - invalid object pointer.", ctx.getMetaruleName()); + } else { + *(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()) = ctx.arg(2).rawValue(); + } } void sf_get_object_ai_data(OpcodeContext& ctx) { diff --git a/sfall/Modules/Scripting/Handlers/Objects.h b/sfall/Modules/Scripting/Handlers/Objects.h index 1a54c3e5..8c048b02 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.h +++ b/sfall/Modules/Scripting/Handlers/Objects.h @@ -101,6 +101,10 @@ void sf_get_obj_under_cursor(OpcodeContext&); void sf_get_loot_object(OpcodeContext&); +void sf_get_proto_data(OpcodeContext&); + +void sf_set_proto_data(OpcodeContext&); + void sf_get_object_data(OpcodeContext&); void sf_set_object_data(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index a470bfa8..e33371e3 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -30,12 +30,10 @@ namespace script void __declspec(naked) op_get_perk_owed() { __asm { - push edx; push ecx; movzx edx, byte ptr ds:[FO_VAR_free_perk]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -43,14 +41,12 @@ void __declspec(naked) op_get_perk_owed() { void __declspec(naked) op_set_perk_owed() { __asm { push ecx; - push edx; _GET_ARG_INT(end); and eax, 0xFF; cmp eax, 250; jg end; mov byte ptr ds:[FO_VAR_free_perk], al; end: - pop edx; pop ecx; retn; } @@ -59,12 +55,10 @@ end: void __declspec(naked) op_set_perk_freq() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call SetPerkFreq; end: - pop edx; pop ecx; retn; } @@ -309,12 +303,10 @@ void sf_has_fake_trait_npc(OpcodeContext& ctx) { void __declspec(naked) op_perk_add_mode() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; call AddPerkMode; end: - pop edx; pop ecx; retn; } @@ -347,13 +339,11 @@ next: void __declspec(naked) op_set_pyromaniac_mod() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; push 0x424AB6; call SafeWrite8; end: - pop edx; pop ecx; retn; } @@ -371,17 +361,68 @@ void __declspec(naked) op_apply_heaveho_fix() { void __declspec(naked) op_set_swiftlearner_mod() { __asm { push ecx; - push edx; _GET_ARG_INT(end); push eax; push 0x4AFAE2; call SafeWrite32; end: - pop edx; pop ecx; retn; } } +static void __declspec(naked) perk_can_add_hook() { + __asm { + call fo::funcoffs::stat_pc_get_; + add eax, Perks::PerkLevelMod; + js jneg; // level < 0 + retn; +jneg: + xor eax, eax; + retn; + } +} + +static void __fastcall SetPerkLevelMod(long mod) { + static bool perkLevelModPatch = false; + if (mod < -25 || mod > 25) return; + Perks::PerkLevelMod = mod; + + if (perkLevelModPatch) return; + perkLevelModPatch = true; + HookCall(0x49687F, perk_can_add_hook); +} + +void __declspec(naked) op_set_perk_level_mod() { + __asm { + push ecx; + _GET_ARG_INT(end); + mov ecx, eax; + call SetPerkLevelMod; +end: + pop ecx; + retn; + } +} + +void sf_add_trait(OpcodeContext& ctx) { + if (fo::var::obj_dude->protoId != fo::PID_Player) { + ctx.printOpcodeError("%s() - traits can be added only to the player.", ctx.getMetaruleName()); + return; + } + long traitId = ctx.arg(0).rawValue(); + if (traitId >= fo::TRAIT_fast_metabolism && traitId <= fo::TRAIT_gifted) { + if (fo::var::pc_trait[0] == -1) { + fo::var::pc_trait[0] = traitId; + } else if (fo::var::pc_trait[0] != traitId && fo::var::pc_trait[1] == -1) { + fo::var::pc_trait[1] = traitId; + } else { + ctx.printOpcodeError("%s() - cannot add the trait ID: %d", ctx.getMetaruleName(), traitId); + } + } else { + ctx.printOpcodeError("%s() - invalid trait ID.", ctx.getMetaruleName()); + } +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Perks.h b/sfall/Modules/Scripting/Handlers/Perks.h index 1e2d6c84..9b60aed5 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.h +++ b/sfall/Modules/Scripting/Handlers/Perks.h @@ -75,5 +75,9 @@ void __declspec() op_apply_heaveho_fix(); void __declspec() op_set_swiftlearner_mod(); +void __declspec() op_set_perk_level_mod(); + +void sf_add_trait(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Stats.cpp b/sfall/Modules/Scripting/Handlers/Stats.cpp index e64975d9..b4db3776 100644 --- a/sfall/Modules/Scripting/Handlers/Stats.cpp +++ b/sfall/Modules/Scripting/Handlers/Stats.cpp @@ -19,6 +19,7 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\Combat.h" #include "..\..\Criticals.h" +#include "..\..\CritterStats.h" #include "..\..\ScriptExtender.h" #include "..\..\Skills.h" #include "..\..\Stats.h" @@ -79,7 +80,7 @@ void sf_set_critter_base_stat(OpcodeContext& ctx) { if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { - Stats::SetStat(obj, stat, ctx.arg(2).rawValue(), 9); + CritterStats::SetStat(obj, stat, ctx.arg(2).rawValue(), 9); } else { ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } @@ -93,7 +94,7 @@ void sf_set_critter_extra_stat(OpcodeContext& ctx) { if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { - Stats::SetStat(obj, stat, ctx.arg(2).rawValue(), 44); + CritterStats::SetStat(obj, stat, ctx.arg(2).rawValue(), 44); } else { ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } @@ -108,7 +109,7 @@ void sf_get_critter_base_stat(OpcodeContext& ctx) { if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { - result = Stats::GetStat(obj, stat, 9); + result = CritterStats::GetStat(obj, stat, 9); } else { ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } @@ -124,7 +125,7 @@ void sf_get_critter_extra_stat(OpcodeContext& ctx) { if (obj && obj->Type() == fo::OBJ_TYPE_CRITTER) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { - result = Stats::GetStat(obj, stat, 44); + result = CritterStats::GetStat(obj, stat, 44); } else { ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } @@ -236,13 +237,11 @@ end: void __declspec(naked) op_set_available_skill_points() { __asm { push ecx; - push edx; _GET_ARG_INT(end); mov edx, eax; xor eax, eax; call fo::funcoffs::stat_pc_set_; end: - pop edx; pop ecx; retn; } @@ -250,12 +249,10 @@ end: void __declspec(naked) op_get_available_skill_points() { __asm { - push edx; push ecx; mov edx, dword ptr ds:[FO_VAR_curr_pc_stat]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } @@ -263,7 +260,6 @@ void __declspec(naked) op_get_available_skill_points() { void __declspec(naked) op_mod_skill_points_per_level() { __asm { push ecx; - push edx; _GET_ARG_INT(end); cmp eax, 100; jg end; @@ -274,7 +270,6 @@ void __declspec(naked) op_mod_skill_points_per_level() { push 0x43C27A; call SafeWrite8; end: - pop edx; pop ecx; retn; } @@ -875,88 +870,29 @@ end: } } -static float xpmod; -static DWORD xptmp; -static void __declspec(naked) SetXpMod3() { +static DWORD xpTemp; +static void __declspec(naked) statPCAddExperienceCheckPMs_hack() { __asm { - push ebx; - push ecx; - push esi; - push edi; - push ebp; - mov xptmp, eax; - fild xptmp; - fmul xpmod; - fistp xptmp; - mov eax, xptmp; - push 0x4AFABD; - retn; + mov ebp, [esp]; // return addr + mov xpTemp, eax; // experience + fild xpTemp; + fmul Stats::experienceMod; + fistp xpTemp; + mov eax, xpTemp; + sub esp, 0xC; // instead of 0x10 + mov edi, eax; + jmp ebp; } } -static void _stdcall SetXpMod2(DWORD percent) { - MakeJump(0x4AFAB8, SetXpMod3); - xpmod = (float)percent / 100.0f; -} +void sf_set_xp_mod(OpcodeContext& ctx) { + static bool xpModPatch = false; + DWORD percent = ctx.arg(0).rawValue() & 0xFFFF; + Stats::experienceMod = (float)percent / 100.0f; -static int PerkLevelMod; -static void __declspec(naked) SetPerkLevelMod3() { - __asm { - push ebx; - call fo::funcoffs::stat_pc_get_; - pop ebx; - add eax, PerkLevelMod; - cmp eax, 0; - jge end; - xor eax, eax; -end: - retn; - } -} - -static void _stdcall SetPerkLevelMod2(int mod) { - if (mod < -25 || mod > 25) return; - PerkLevelMod = mod; - HookCall(0x49687F, &SetPerkLevelMod3); -} - -void __declspec(naked) op_set_xp_mod() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_INT; - jnz end; - and eax, 0xFFFF; - push eax; - call SetXpMod2; -end: - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } -} - -void __declspec(naked) op_set_perk_level_mod() { - __asm { - push ecx; - push edx; - _GET_ARG_INT(end); - push eax; - call SetPerkLevelMod2; -end: - pop edx; - pop ecx; - retn; - } + if (xpModPatch) return; + xpModPatch = true; + MakeCall(0x4AFABD, statPCAddExperienceCheckPMs_hack); } } diff --git a/sfall/Modules/Scripting/Handlers/Stats.h b/sfall/Modules/Scripting/Handlers/Stats.h index df8062a9..90b6f608 100644 --- a/sfall/Modules/Scripting/Handlers/Stats.h +++ b/sfall/Modules/Scripting/Handlers/Stats.h @@ -84,9 +84,7 @@ void __declspec() op_set_npc_stat_max(); void __declspec() op_set_npc_stat_min(); -void __declspec() op_set_xp_mod(); - -void __declspec() op_set_perk_level_mod(); +void sf_set_xp_mod(OpcodeContext&); } } diff --git a/sfall/Modules/Scripting/Handlers/Worldmap.cpp b/sfall/Modules/Scripting/Handlers/Worldmap.cpp index 50bc6bd7..7afb68fd 100644 --- a/sfall/Modules/Scripting/Handlers/Worldmap.cpp +++ b/sfall/Modules/Scripting/Handlers/Worldmap.cpp @@ -89,13 +89,11 @@ void sf_force_encounter(OpcodeContext& cxt) { void __declspec(naked) op_in_world_map() { __asm { push ecx; - push edx; push eax; call InWorldMap; mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } @@ -104,13 +102,11 @@ void __declspec(naked) op_in_world_map() { void __declspec(naked) op_get_game_mode() { __asm { push ecx; - push edx; push eax; call GetLoopFlags; mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; pop ecx; retn; } @@ -118,24 +114,20 @@ void __declspec(naked) op_get_game_mode() { void __declspec(naked) op_get_world_map_x_pos() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_world_xpos]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } void __declspec(naked) op_get_world_map_y_pos() { __asm { - push edx; push ecx; mov edx, ds:[FO_VAR_world_ypos]; _RET_VAL_INT(ecx); pop ecx; - pop edx; retn; } } diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index b2da066c..fb0f18cd 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -86,6 +86,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x19e, "get_sfall_global_int", sf_get_sfall_global_int, 1, true, {ARG_INTSTR}}, {0x19f, "get_sfall_global_float", sf_get_sfall_global_float, 1, true, {ARG_INTSTR}}, {0x1a5, "inc_npc_level", sf_inc_npc_level, 1, false, {ARG_INTSTR}}, + {0x1aa, "set_xp_mod", sf_set_xp_mod, 1, false, {ARG_INT}}, {0x1ac, "get_ini_setting", sf_get_ini_setting, 1, true, {ARG_STRING}}, {0x1bb, "set_fake_perk", sf_set_fake_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, @@ -159,6 +160,8 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x237, "atoi", sf_atoi, 1, true, {ARG_STRING}}, {0x238, "atof", sf_atof, 1, true, {ARG_STRING}}, {0x239, "scan_array", sf_scan_array, 2, true, {ARG_OBJECT, ARG_ANY}}, + {0x23c, "get_sfall_args", sf_get_sfall_args, 0, true}, + {0x23d, "set_sfall_arg", sf_set_sfall_arg, 2, false, {ARG_INT, ARG_INT}}, {0x241, "get_npc_level", sf_get_npc_level, 1, true, {ARG_INTSTR}}, {0x24e, "substr", sf_substr, 3, true, {ARG_STRING, ARG_INT, ARG_INT}}, @@ -260,7 +263,7 @@ void InitNewOpcodes() { SafeWrite32(0x46CE6C, (DWORD)opcodes); // call that actually jumps to the opcode SafeWrite32(0x46E390, (DWORD)opcodes); // mov that writes to the opcode - if (isDebug && (GetPrivateProfileIntA("Debugging", "AllowUnsafeScripting", 0, ::sfall::ddrawIni) != 0)) { + if (iniGetInt("Debugging", "AllowUnsafeScripting", 0, ::sfall::ddrawIni)) { dlogr(" Unsafe opcodes enabled.", DL_SCRIPT); opcodes[0x1cf] = op_write_byte; opcodes[0x1d0] = op_write_short; @@ -327,7 +330,6 @@ void InitNewOpcodes() { opcodes[0x1a7] = op_get_viewport_y; opcodes[0x1a8] = op_set_viewport_x; opcodes[0x1a9] = op_set_viewport_y; - opcodes[0x1aa] = op_set_xp_mod; opcodes[0x1ab] = op_set_perk_level_mod; opcodes[0x1ad] = op_get_shader_version; opcodes[0x1ae] = op_set_shader_mode; @@ -394,8 +396,6 @@ void InitNewOpcodes() { opcodes[0x23a] = op_get_tile_fid; opcodes[0x23b] = op_modified_ini; - opcodes[0x23c] = op_get_sfall_args; - opcodes[0x23d] = op_set_sfall_arg; opcodes[0x23e] = op_force_aimed_shots; opcodes[0x23f] = op_disable_aimed_shots; opcodes[0x240] = op_mark_movie_played; diff --git a/sfall/Modules/Skills.cpp b/sfall/Modules/Skills.cpp index 45bbb4da..f9d9bb2f 100644 --- a/sfall/Modules/Skills.cpp +++ b/sfall/Modules/Skills.cpp @@ -331,17 +331,21 @@ void Skills::init() { // 0x00000400 - Energy Weapon (forces weapon to use Energy Weapons skill) HookCall(0x47831E, item_w_skill_hook); + LoadGameHook::OnGameReset() += Skills_OnGameLoad; + char buf[512], key[16]; auto skillsFile = GetConfigString("Misc", "SkillsFile", "", MAX_PATH); if (!skillsFile.empty()) { fo::SkillInfo *skills = fo::var::skill_data; const char* file = skillsFile.insert(0, ".\\").c_str(); + if (GetFileAttributes(file) == INVALID_FILE_ATTRIBUTES) return; + multipliers = new double[7 * fo::SKILL_count](); for (int i = 0; i < fo::SKILL_count; i++) { sprintf(key, "Skill%d", i); - if (GetPrivateProfileStringA("Skills", key, "", buf, 64, file)) { + if (iniGetString("Skills", key, "", buf, 64, file)) { char* tok = strtok(buf, "|"); while (tok) { if (strlen(tok) >= 2) { @@ -365,7 +369,7 @@ void Skills::init() { if (skills[i].statB >= 0) multipliers[i * 7 + skills[i].statB] = skills[i].statMulti; } sprintf(key, "SkillCost%d", i); - if (GetPrivateProfileStringA("Skills", key, "", buf, 512, file)) { + if (iniGetString("Skills", key, "", buf, 512, file)) { char* tok = strtok(buf, "|"); DWORD upto = 0; BYTE price = 1; @@ -387,15 +391,15 @@ void Skills::init() { for (int j = 201; j <= 512; j++) skillCosts[i * 512 + j] = 6; } sprintf(key, "SkillBase%d", i); - skills[i].base = GetPrivateProfileIntA("Skills", key, skills[i].base, file); + skills[i].base = iniGetInt("Skills", key, skills[i].base, file); sprintf(key, "SkillMulti%d", i); - int multi = GetPrivateProfileIntA("Skills", key, skills[i].skillPointMulti, file); + int multi = iniGetInt("Skills", key, skills[i].skillPointMulti, file); if (multi < 1) multi = 1; else if (multi > 10) multi = 10; skills[i].skillPointMulti = multi; sprintf(key, "SkillImage%d", i); - skills[i].image = GetPrivateProfileIntA("Skills", key, skills[i].image, file); + skills[i].image = iniGetInt("Skills", key, skills[i].image, file); } MakeJump(0x4AA59D, skill_level_hack_bonus, 1); @@ -403,11 +407,12 @@ void Skills::init() { MakeJump(0x4AA940, skill_dec_point_hack_cost, 1); HookCalls(skill_dec_point_hook_cost, {0x4AA9E1, 0x4AA9F1}); - basedOnPoints = GetPrivateProfileIntA("Skills", "BasedOnPoints", 0, file); + basedOnPoints = iniGetInt("Skills", "BasedOnPoints", 0, file); if (basedOnPoints) HookCall(0x4AA9EC, (void*)fo::funcoffs::skill_points_); // skill_dec_point_ } - - LoadGameHook::OnGameReset() += Skills_OnGameLoad; } +void Skills::exit() { + delete[] multipliers; +} } diff --git a/sfall/Modules/Skills.h b/sfall/Modules/Skills.h index a8d83f88..9a3333a2 100644 --- a/sfall/Modules/Skills.h +++ b/sfall/Modules/Skills.h @@ -27,6 +27,7 @@ class Skills : public Module { public: const char* name() { return "Skills"; } void init(); + void exit() override; }; void _stdcall SetSkillMax(fo::GameObject* critter, int maximum); diff --git a/sfall/Modules/Sound.cpp b/sfall/Modules/Sound.cpp index fe7f57a7..646d4d24 100644 --- a/sfall/Modules/Sound.cpp +++ b/sfall/Modules/Sound.cpp @@ -64,7 +64,7 @@ void Sound::init() { HookCall(0x42B849, ai_print_msg_hook); //Yes, I did leave this in on purpose. Will be of use to anyone trying to add in the sound effects - if (isDebug && GetPrivateProfileIntA("Debugging", "Test_ForceFloats", 0, ::sfall::ddrawIni)) { + if (isDebug && iniGetInt("Debugging", "Test_ForceFloats", 0, ::sfall::ddrawIni)) { SafeWrite8(0x42B6F5, 0xEB); // bypass chance } } diff --git a/sfall/Modules/Stats.cpp b/sfall/Modules/Stats.cpp index 49fc22ec..fe9ec1ac 100644 --- a/sfall/Modules/Stats.cpp +++ b/sfall/Modules/Stats.cpp @@ -16,78 +16,16 @@ * along with this program. If not, see . */ -#include -#include - #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "CritterStats.h" #include "LoadGameHook.h" -#include "Objects.h" #include "Stats.h" namespace sfall { -static long* __fastcall GetProto(fo::GameObject*); - -struct ProtoMem { - long* proto; - long pid; - long sharedCount; // if the value is 0, then the prototype can be deleted from memory - - ProtoMem() {} - ProtoMem(long pid) : proto(nullptr), pid(pid), sharedCount(0) {} - ProtoMem(long* proto, long pid) : proto(proto), pid(pid), sharedCount(1) {} - - ~ProtoMem() { delete[] proto; } -}; -std::unordered_map protoMem; // key - critter ID, value - pointer to critter prototype for unique ID - -typedef std::unordered_map::iterator protoMem_iterator; -static long CreateProtoMem(const protoMem_iterator&, long*, long*&); - -struct ProtoModify { - long objID; - long objPID; - long stat; - long amount; - long defval; - long* s_proto; // shared pointer for quick access to the prototype in memory (should not be saved to file) - - ProtoModify() : s_proto(nullptr) {} - ProtoModify(fo::GameObject* critter, long stat, long amount, long* defaultProto, long defVal) { - objID = Objects::SetObjectUniqueID(critter); - objPID = critter->protoId; - this->stat = stat; - this->amount = amount; - this->defval = defVal; - - auto it = protoMem.find(objID); - if (it == protoMem.end()) { - long* newProto = new long[104]; // 416 bytes - memcpy(newProto, defaultProto, 416); - s_proto = newProto; - protoMem.emplace( - std::piecewise_construct, - std::forward_as_tuple(objID), - std::forward_as_tuple(newProto, objPID)); - } else { - s_proto = it->second.proto; - if (s_proto == nullptr) { // prototype has not been created - it->second.sharedCount = CreateProtoMem(it, defaultProto, s_proto); - it->second.proto = s_proto; - } - it->second.sharedCount++; - } - } -}; -std::vector bonusStatProto; -std::vector baseStatProto; -// saved -std::vector s_bonusStatProto; -std::vector s_baseStatProto; - static DWORD statMaximumsPC[fo::STAT_max_stat]; static DWORD statMinimumsPC[fo::STAT_max_stat]; static DWORD statMaximumsNPC[fo::STAT_max_stat]; @@ -95,6 +33,10 @@ static DWORD statMinimumsNPC[fo::STAT_max_stat]; static DWORD xpTable[99]; +float Stats::experienceMod = 1.0f; // set_xp_mod func +DWORD Stats::standardApAcBonus = 4; +DWORD Stats::extraApAcBonus = 4; + static struct StatFormula { long base; long min; @@ -102,9 +44,6 @@ static struct StatFormula { double multi[fo::STAT_lu + 1]; } statFormulas[fo::STAT_max_derived + 1] = {0}; -DWORD standardApAcBonus = 4; -DWORD extraApAcBonus = 4; - static fo::GameObject* cCritter; static const DWORD StatLevelHack_Ret = 0x4AEF52; @@ -195,12 +134,12 @@ static void __declspec(naked) CalcApToAcBonus() { mov edx, PERK_hth_evade_perk; mov eax, dword ptr ds:[FO_VAR_obj_dude]; call fo::funcoffs::perk_level_; - imul eax, extraApAcBonus; // bonus = perkLvl * extraApBonus - imul eax, edi; // perkBonus = bonus * curAP + imul eax, Stats::extraApAcBonus; // bonus = perkLvl * extraApBonus + imul eax, edi; // perkBonus = bonus * curAP standard: - imul edi, standardApAcBonus; // stdBonus = curAP * standardApBonus - add eax, edi; // bonus = perkBonus + stdBonus - shr eax, 2; // acBonus = bonus / 4 + imul edi, Stats::standardApAcBonus; // stdBonus = curAP * standardApBonus + add eax, edi; // bonus = perkBonus + stdBonus + shr eax, 2; // acBonus = bonus / 4 end: retn; } @@ -210,7 +149,7 @@ static void _stdcall StatRecalcDerived(fo::GameObject* critter) { int baseStats[7]; for (int stat = fo::Stat::STAT_st; stat <= fo::Stat::STAT_lu; stat++) baseStats[stat] = fo::func::stat_level(critter, stat); - long* proto = GetProto(critter); + long* proto = CritterStats::GetProto(critter); if (!proto) fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto); for (int i = fo::Stat::STAT_max_hit_points; i <= fo::Stat::STAT_poison_resist; i++) { @@ -222,7 +161,7 @@ static void _stdcall StatRecalcDerived(fo::GameObject* critter) { } long calcStat = statFormulas[i].base + (int)floor(sum); if (calcStat < statFormulas[i].min) calcStat = statFormulas[i].min; - proto[i + 9] = calcStat; // offset from base_stat_srength + proto[OffsetStat::base + i] = calcStat; // offset from base_stat_srength } } @@ -238,326 +177,44 @@ static void __declspec(naked) stat_recalc_derived_hack() { } } -///////////////////// CRITTERS STATS ///////////////////// -static long isNotPartyMemberPid; - -static struct { - long id; - long* proto; -} lastGetProtoID; - -// offset 44 - from bonus_stat_srength, 9 - from base_stat_srength -static long GetStatValue(long* proto, long offset) { - return proto[offset]; -} - -static void SetStatValue(long* proto, long offset, long amount) { - proto[offset] = amount; -} - -static long ApplyAllStatsToProto(const protoMem_iterator &iter, long* &proto_out) { - long count = 0; - for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { - if (itBonus->objID == iter->first && itBonus->objPID == iter->second.pid) { - if (!proto_out && !fo::CritterCopyProto(iter->second.pid, proto_out)) return 0; - itBonus->s_proto = proto_out; - SetStatValue(proto_out, 44 + itBonus->stat, itBonus->amount); - count++; - } - } - for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { - if (itBase->objID == iter->first && itBase->objPID == iter->second.pid) { - if (!proto_out && !fo::CritterCopyProto(iter->second.pid, proto_out)) return 0; - itBase->s_proto = proto_out; - SetStatValue(proto_out, 9 + itBase->stat, itBase->amount); - count++; - } - } - return count; -} - -// Returns the prototype NPC located in memory -static long* __fastcall GetProto(fo::GameObject* critter) { - if (protoMem.empty() || critter->protoId == fo::PID_Player) return nullptr; - if (lastGetProtoID.id == critter->id) return lastGetProtoID.proto; - - long* proto = nullptr; - auto it = protoMem.find(critter->id); - if (it != protoMem.end() && it->second.pid == critter->protoId) { - proto = it->second.proto; - if (!proto) { // there is no prototype in memory, create a new one from the default prototype - it->second.sharedCount = ApplyAllStatsToProto(it, proto); - it->second.proto = proto; - } - if (proto) { - lastGetProtoID.id = it->first; - lastGetProtoID.proto = proto; - } - } - return proto; -} - -static long CreateProtoMem(const protoMem_iterator &it, long* defaultProto, long* &newProto) { - newProto = new long[104]; // 416 bytes - memcpy(newProto, defaultProto, 416); - return ApplyAllStatsToProto(it, newProto); -} - -static long* ReleaseProtoMem(const std::vector::iterator &itStat) { - long* proto = nullptr; - auto it = protoMem.find(itStat->objID); - if (it != protoMem.end() && it->second.pid == itStat->objPID) { - if (it->second.sharedCount <= 1) { // the counter will be reset - if (it->first == lastGetProtoID.id) lastGetProtoID.id = 0; - protoMem.erase(it); // removes the prototype from memory - } else { - it->second.sharedCount--; - proto = it->second.proto; - } - } - return proto; -} - -static std::vector& GetRefVector(long extra, bool isSaved) { - if (isSaved) { - return (extra) ? s_bonusStatProto : s_baseStatProto; - } - return (extra) ? bonusStatProto : baseStatProto;; -} - -static void AddStatToProto(long stat, fo::GameObject* critter, long amount, long* defaultProto, long offset, bool isSaved) { - std::vector &vec = GetRefVector(offset == 44, isSaved); - offset += stat; - - for (auto itStat = vec.begin(); itStat != vec.end(); itStat++) { - if (itStat->objID == critter->id && itStat->objPID == critter->protoId && itStat->stat == stat) { - if (amount == itStat->defval) { // set value and value in default prototype are matched - long* proto = ReleaseProtoMem(itStat); - if (proto) SetStatValue(proto, offset, amount); - // removal of a vector element, without re-moving other elements - if (itStat != vec.cend() - 1) *itStat = vec.back(); - vec.pop_back(); - return; - } - itStat->amount = amount; - if (itStat->s_proto == nullptr) itStat->s_proto = GetProto(critter); - SetStatValue(itStat->s_proto, offset, amount); - return; - } - } - if (vec.capacity() == vec.size()) vec.reserve(vec.size() + 20); - vec.emplace_back(critter, stat, amount, defaultProto, defaultProto[offset]); - SetStatValue(vec.back().s_proto, offset, amount); // set value to individuals prototype -} - -static void __fastcall SetStatToProto(long stat, fo::GameObject* critter, long amount, long* protoBase, long offset, bool isSaved) { - if (critter->protoId != fo::PID_Player) { - if (isNotPartyMemberPid == critter->protoId || !IsPartyMember(critter)) { - isNotPartyMemberPid = critter->protoId; - AddStatToProto(stat, critter, amount, protoBase, offset, isSaved); - return; - } - } - SetStatValue(protoBase, offset + stat, amount); // set value to default prototype -} - -static void __declspec(naked) stat_set_bonus_hack() { +static const DWORD StatSetBaseRet = 0x4AF559; +static void __declspec(naked) stat_set_base_hack_allow() { + using namespace fo; __asm { - pushadc; - mov eax, [esp + 16]; // buf proto - sub eax, 0x20; - push 1; - push 44; // offset from bonus_stat_srength - push eax; // regular proto - push ebx; // amount - mov edx, esi; // critter - call SetStatToProto; // ecx - stat - popadc; + cmp ecx, STAT_unused; + je allow; + cmp ecx, STAT_dmg_thresh; + jl notAllow; + cmp ecx, STAT_dmg_resist_explosion; + jg notAllow; +allow: + pop eax; // destroy return address + jmp StatSetBaseRet; +notAllow: + mov eax, -1; // overwritten engine code retn; } } -static void __declspec(naked) stat_set_base_hack() { +static const DWORD SetCritterStatRet = 0x455D8A; +static void __declspec(naked) op_set_critter_stat_hack() { + using namespace fo; __asm { - pushadc; - mov eax, [esp + 16]; // buf proto - sub eax, 0x20; - push 1; - push 9; // offset from base_stat_srength - push eax; // regular proto - push ebx; // amount - mov edx, esi; // critter - call SetStatToProto; // ecx - stat - popadc; - test ecx, ecx; + cmp dword ptr [esp + 0x2C - 0x28 + 4], STAT_unused; + je allow; + mov ebx, 3; // overwritten engine code retn; +allow: + add esp, 4; // destroy return address + jmp SetCritterStatRet; } } -static void __declspec(naked) stat_get_proto() { - using namespace fo::Fields; - __asm { - push eax; - push ecx; - mov ecx, eax; // critter; - call GetProto; - pop ecx; - test eax, eax; - jz skip; - add esp, 4; - mov [esp + 4], eax; // replace proto - add [esp], 5; // skip call proto_ptr_ - retn; -skip: - pop eax; - lea edx, [esp + 4]; // buf - mov eax, [eax + protoId]; - retn; - } -} - -static void __declspec(naked) stat_recalc_derived_hook() { - __asm { - pushadc; - mov ecx, ebx; - call GetProto; - pop ecx; - pop edx; - test eax, eax; - jz skip; - add esp, 4; - mov [esp + 4], eax; // replace proto - retn; -skip: - pop eax; - jmp fo::funcoffs::proto_ptr_; - } -} - -static void SetStatToAllProtos(long offset, long pid, long amount) { - offset /= 4; - for (const auto& elem : protoMem) { - if (elem.second.pid == pid) { - long* proto = elem.second.proto; - if (proto) SetStatValue(proto, offset, amount); - } - }; -} - -// set_proto_data - sets stats to default prototype and all individual prototypes (the same PID) -long Stats::SetProtoData(long pid, long offset, long amount) { - long* proto; - long result = fo::func::proto_ptr(pid, (fo::Proto**)&proto); - if (result != -1) { - if (!protoMem.empty() && pid != fo::PID_Player && (pid >> 24) == fo::OBJ_TYPE_CRITTER && (offset >= 0x24 && offset <= 0x138)) { - SetStatToAllProtos(offset, pid, amount); - } - *(long*)((BYTE*)proto + offset) = amount; // set to default prototype - } - return result; -} - -// get_critter_*_stat - gets stats from an individual's prototype or from a default prototype -long Stats::GetStat(fo::GameObject* critter, long stat, long offset) { - long* proto = GetProto(critter); - if (proto == nullptr) fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto); - return (proto) ? GetStatValue(proto, stat + offset) : 0; // direct get -} - -// set_critter_*_stat - sets stats to an individual's critter prototype -void Stats::SetStat(fo::GameObject* critter, long stat, long amount, long offset) { - long* proto; - if (fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto) != -1) { - SetStatToProto(stat, critter, amount, proto, offset, false); // not save stat - } -} - -void Stats::SaveStatData(HANDLE file) { - DWORD sizeWrite, count = s_baseStatProto.size(); - WriteFile(file, &count, 4, &sizeWrite, 0); - for (size_t i = 0; i < count; i++) { - WriteFile(file, &s_baseStatProto[i], 20, &sizeWrite, 0); - } - count = s_bonusStatProto.size(); - WriteFile(file, &count, 4, &sizeWrite, 0); - for (size_t i = 0; i < count; i++) { - WriteFile(file, &s_bonusStatProto[i], 20, &sizeWrite, 0); - } -} - -bool Stats::LoadStatData(HANDLE file) { - DWORD count, sizeRead; - ReadFile(file, &count, 4, &sizeRead, 0); - //if (sizeRead != 4) return true; - if (count) s_baseStatProto.reserve(count + 10); - for (size_t i = 0; i < count; i++) { - ProtoModify data; - ReadFile(file, &data, 20, &sizeRead, 0); - if (sizeRead != 20) return true; - s_baseStatProto.emplace_back(data); - if (protoMem.find(data.objID) == protoMem.end()) { - protoMem.emplace(data.objID, data.objPID); - } - } - ReadFile(file, &count, 4, &sizeRead, 0); - //if (sizeRead != 4) return true; - if (count) s_bonusStatProto.reserve(count + 10); - for (size_t i = 0; i < count; i++) { - ProtoModify data; - ReadFile(file, &data, 20, &sizeRead, 0); - if (sizeRead != 20) return true; - s_bonusStatProto.emplace_back(data); - if (protoMem.find(data.objID) == protoMem.end()) { - protoMem.emplace(data.objID, data.objPID); - } - } - return false; -} - -static void ClearAllStats() { - bonusStatProto.clear(); - baseStatProto.clear(); - lastGetProtoID.id = 0; -} - -static void FlushAllProtos() { - for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { - itBonus->s_proto = nullptr; - } - for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { - itBase->s_proto = nullptr; - } - for (auto& elem : protoMem) { - delete[] elem.second.proto; - elem.second.proto = nullptr; - elem.second.sharedCount = 0; - }; - ClearAllStats(); -} - -static void __declspec(naked) map_save_in_game_hook() { - __asm { - call FlushAllProtos; - jmp fo::funcoffs::proto_remove_all_; - } -} - -void StatsProtoReset() { - ClearAllStats(); - s_bonusStatProto.clear(); - s_baseStatProto.clear(); - protoMem.clear(); - isNotPartyMemberPid = 0; -} -/////////////////////////////////////////////////////////// - -void StatsReset() { +static void StatsReset() { for (int i = 0; i < fo::STAT_max_stat; i++) { statMaximumsPC[i] = statMaximumsNPC[i] = fo::var::stat_data[i].maxValue; statMinimumsPC[i] = statMinimumsNPC[i] = fo::var::stat_data[i].minValue; } - standardApAcBonus = 4; - extraApAcBonus = 4; } void Stats::init() { @@ -567,14 +224,14 @@ void Stats::init() { LoadGameHook::OnGameReset() += []() { StatsReset(); - StatsProtoReset(); - //Reset some settable game values back to the defaults - //xp mod - SafeWrite8(0x4AFAB8, 0x53); - SafeWrite32(0x4AFAB9, 0x55575651); - //HP bonus + // Reset some settable game values back to the defaults + standardApAcBonus = 4; + extraApAcBonus = 4; + // XP mod set to 100% + experienceMod = 1.0f; + // HP bonus SafeWrite8(0x4AFBC1, 2); - //skill points per level mod + // Skill points per level mod SafeWrite8(0x43C27A, 5); }; @@ -584,6 +241,10 @@ void Stats::init() { MakeCall(0x4AF09C, CalcApToAcBonus, 3); // stat_level_ + // Allow set_critter_stat function to change STAT_unused and STAT_dmg_* stats for the player + MakeCall(0x4AF54E, stat_set_base_hack_allow); + MakeCall(0x455D65, op_set_critter_stat_hack); // STAT_unused for other critters + auto xpTableList = GetConfigList("Misc", "XPTable", "", 2048); size_t numLevels = xpTableList.size(); if (numLevels > 0) { @@ -635,33 +296,26 @@ void Stats::init() { char key[6], buf2[256], buf3[256]; const char* statFile = statsFile.insert(0, ".\\").c_str(); + if (GetFileAttributes(statFile) == INVALID_FILE_ATTRIBUTES) return; for (int i = fo::Stat::STAT_max_hit_points; i <= fo::Stat::STAT_poison_resist; i++) { if (i >= fo::Stat::STAT_dmg_thresh && i <= fo::Stat::STAT_dmg_resist_explosion) continue; _itoa(i, key, 10); - statFormulas[i].base = GetPrivateProfileInt(key, "base", statFormulas[i].base, statFile); - statFormulas[i].min = GetPrivateProfileInt(key, "min", statFormulas[i].min, statFile); + statFormulas[i].base = iniGetInt(key, "base", statFormulas[i].base, statFile); + statFormulas[i].min = iniGetInt(key, "min", statFormulas[i].min, statFile); for (int j = 0; j < fo::Stat::STAT_max_hit_points; j++) { sprintf(buf2, "shift%d", j); - statFormulas[i].shift[j] = GetPrivateProfileInt(key, buf2, statFormulas[i].shift[j], statFile); + statFormulas[i].shift[j] = iniGetInt(key, buf2, statFormulas[i].shift[j], statFile); sprintf(buf2, "multi%d", j); _gcvt(statFormulas[i].multi[j], 16, buf3); - GetPrivateProfileStringA(key, buf2, buf3, buf2, 256, statFile); + iniGetString(key, buf2, buf3, buf2, 256, statFile); statFormulas[i].multi[j] = atof(buf2); } } } else { - HookCall(0x4AF761, stat_recalc_derived_hook); + CritterStats::RecalcDerivedHook(); } - - MakeCall(0x4AF6AD, stat_set_bonus_hack, 1); - MakeCall(0x4AF5B8, stat_set_base_hack); - MakeCalls(stat_get_proto, { - 0x4AF49B, // stat_get_bonus_ - 0x4AF455 // stat_get_base_direct_ - }); - HookCall(0x483E0B, map_save_in_game_hook); // remove all protos } void _stdcall SetPCStatMax(int stat, int i) { diff --git a/sfall/Modules/Stats.h b/sfall/Modules/Stats.h index 28227cd9..13c009e8 100644 --- a/sfall/Modules/Stats.h +++ b/sfall/Modules/Stats.h @@ -28,19 +28,14 @@ public: const char* name() { return "Stats"; } void init(); - static long GetStat(fo::GameObject* critter, long stat, long offset); - static void SetStat(fo::GameObject* critter, long stat, long amount, long offset); - static long SetProtoData(long pid, long offset, long amount); - - static void SaveStatData(HANDLE file); - static bool LoadStatData(HANDLE file); + static float experienceMod; + static DWORD standardApAcBonus; + static DWORD extraApAcBonus; }; void _stdcall SetPCStatMax(int stat, int i); void _stdcall SetPCStatMin(int stat, int i); void _stdcall SetNPCStatMax(int stat, int i); void _stdcall SetNPCStatMin(int stat, int i); -extern DWORD standardApAcBonus; -extern DWORD extraApAcBonus; } diff --git a/sfall/Modules/Worldmap.cpp b/sfall/Modules/Worldmap.cpp index 788d4f96..8439c8ca 100644 --- a/sfall/Modules/Worldmap.cpp +++ b/sfall/Modules/Worldmap.cpp @@ -339,12 +339,12 @@ void WorldLimitsPatches() { dlogr(" Done", DL_INIT); } - //if(GetConfigInt("Misc", "CitiesLimitFix", 0)) { - dlog("Applying cities limit patch.", DL_INIT); - if (*((BYTE*)0x4BF3BB) != 0xEB) { - SafeWrite8(0x4BF3BB, 0xEB); - } - dlogr(" Done", DL_INIT); + //if (GetConfigInt("Misc", "CitiesLimitFix", 0)) { + dlog("Applying cities limit patch.", DL_INIT); + if (*((BYTE*)0x4BF3BB) != 0xEB) { + SafeWrite8(0x4BF3BB, 0xEB); + } + dlogr(" Done", DL_INIT); //} } @@ -418,12 +418,12 @@ void WorldmapFpsPatch() { } void PathfinderFixInit() { - //if(GetConfigInt("Misc", "PathfinderFix", 0)) { - dlog("Applying Pathfinder patch.", DL_INIT); - SafeWrite16(0x4C1FF6, 0x9090); // wmPartyWalkingStep_ - HookCall(0x4C1C78, PathfinderFix); // wmGameTimeIncrement_ - mapMultiMod = (double)GetConfigInt("Misc", "WorldMapTimeMod", 100) / 100.0; - dlogr(" Done", DL_INIT); + //if (GetConfigInt("Misc", "PathfinderFix", 0)) { + dlog("Applying Pathfinder patch.", DL_INIT); + SafeWrite16(0x4C1FF6, 0x9090); // wmPartyWalkingStep_ + HookCall(0x4C1C78, PathfinderFix); // wmGameTimeIncrement_ + mapMultiMod = (double)GetConfigInt("Misc", "WorldMapTimeMod", 100) / 100.0; + dlogr(" Done", DL_INIT); //} } diff --git a/sfall/SafeWrite.cpp b/sfall/SafeWrite.cpp index ec786711..53f168ea 100644 --- a/sfall/SafeWrite.cpp +++ b/sfall/SafeWrite.cpp @@ -1,6 +1,5 @@ #include "main.h" - -#pragma warning(disable:4996) +#include "CheckAddress.h" namespace sfall { @@ -11,47 +10,6 @@ enum CodeType : BYTE { Nop = 0x90 }; -#ifndef NDEBUG -std::multimap writeAddress; - -/* Checking for conflicts requires all options in ddraw.ini to be enabled */ -void PrintAddrList() { - bool level = (GetPrivateProfileIntA("Debugging", "Enable", 0, ::sfall::ddrawIni) > 1); - unsigned long pa = 0, pl = 0; - for (const auto &wa : writeAddress) { - unsigned long diff = (pa) ? (wa.first - pa) : -1; // length between two addresses - if (diff == 0) { - dlog_f("0x%x L:%d [Overwriting]\n", DL_MAIN, wa.first, wa.second); - } else if (diff < pl) { - dlog_f("0x%x L:%d [Conflict] with 0x%x L:%d\n", DL_MAIN, wa.first, wa.second, pa, pl); - } else if (level && diff == pl) { - dlog_f("0x%x L:%d [Warning] PL:%d\n", DL_MAIN, wa.first, wa.second, pl); - } else if (level) { - dlog_f("0x%x L:%d\n", DL_MAIN, wa.first, wa.second); - } - pa = wa.first; - pl = wa.second; - } -} - -void CheckConflict(DWORD addr, long len) { - if (writeAddress.find(addr) != writeAddress.cend()) { - char buf[64]; - sprintf_s(buf, "Memory writing conflict at address 0x%x.", addr); - //dlogr(buf, DL_MAIN); - MessageBoxA(0, buf, "", MB_TASKMODAL); - } - writeAddress.emplace(addr, len); -} - -void AddrAddToList(DWORD addr, long len) { - writeAddress.emplace(addr, len); -} -#else -void CheckConflict(DWORD addr, long len) {} -void AddrAddToList(DWORD addr, long len) {} -#endif - static void _stdcall SafeWriteFunc(BYTE code, DWORD addr, void* func) { DWORD oldProtect, data = (DWORD)func - (addr + 5); diff --git a/sfall/SafeWrite.h b/sfall/SafeWrite.h index e7e6ca8d..6ec9982d 100644 --- a/sfall/SafeWrite.h +++ b/sfall/SafeWrite.h @@ -3,13 +3,10 @@ #include #include -namespace sfall -{ +#include "CheckAddress.h" -#ifndef NDEBUG -void AddrAddToList(DWORD addr, long len); -void PrintAddrList(); -#endif +namespace sfall +{ template void _stdcall SafeWrite(DWORD addr, T data) { DWORD oldProtect; @@ -17,9 +14,7 @@ template void _stdcall SafeWrite(DWORD addr, T data) { *((T*)addr) = data; VirtualProtect((void*)addr, sizeof(T), oldProtect, &oldProtect); - #ifndef NDEBUG - AddrAddToList(addr, sizeof(T)); - #endif + AddrAddToList(addr, sizeof(T)); } template diff --git a/sfall/SimplePatch.h b/sfall/SimplePatch.h index d48292ae..dd650e18 100644 --- a/sfall/SimplePatch.h +++ b/sfall/SimplePatch.h @@ -23,8 +23,9 @@ T SimplePatch(DWORD *addrs, int numAddrs, const char* iniSection, const char* in } _snprintf_s(msg, sizeof(msg), _TRUNCATE, "Applying patch: %s = %d.", iniKey, value); dlog((const char*)msg, DL_INIT); - for (int i = 0; i < numAddrs; i++) + for (int i = 0; i < numAddrs; i++) { SafeWrite(addrs[i], (T)value); + } dlogr(" Done", DL_INIT); } return value; diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 47443b8f..1e57417e 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -45,7 +45,7 @@ DynamicLibrary NotSet - v141 + v140_xp @@ -78,32 +78,15 @@ false false false - true - true - true - - - $(DXSDK_DIR)Include;$(IncludePath);$(ProjectDir) - $(DXSDK_DIR)lib\x86;$(LibraryPath) - - - $(DXSDK_DIR)Include;$(IncludePath);$(ProjectDir) - $(DXSDK_DIR)lib\x86;$(LibraryPath) - - - $(DXSDK_DIR)Include;$(IncludePath);$(ProjectDir) - $(DXSDK_DIR)lib\x86;$(LibraryPath) - - - $(DXSDK_DIR)Include;$(IncludePath);$(ProjectDir) - $(DXSDK_DIR)lib\x86;$(LibraryPath) + false + false + false Disabled INITGUID;WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - true - EnableFastChecks + UninitializedLocalUsageCheck MultiThreadedDebug Use Level3 @@ -111,9 +94,12 @@ $(IntDir)/%(RelativeDir)/ $(IntDir)/%(RelativeDir)/ stdafx.h;%(ForcedIncludeFiles) + $(DXSDK_DIR)Include\ + AssemblyAndSourceCode + false - kernel32.lib;user32.lib;d3d9.lib;d3dx9d.lib;Strmiids.lib;dinput.lib;ws2_32.lib;%(AdditionalDependencies) + d3d9.lib;d3dx9.lib;dinput.lib;Strmiids.lib;ws2_32.lib;%(AdditionalDependencies) exports.def ws2_32.dll;d3d9.dll;d3dx9d_43.dll;%(DelayLoadDLLs) true @@ -130,6 +116,8 @@ false %(IgnoreSpecificDefaultLibraries) + LinkVerbose + $(DXSDK_DIR)lib\x86\ "$(ProjectDir)postbuild.cmd" debug "$(TargetPath)" @@ -148,7 +136,7 @@ Sync MultiThreaded false - NotSet + StreamingSIMDExtensions2 Fast false false @@ -160,9 +148,10 @@ $(IntDir)/%(RelativeDir)/ stdafx.h;%(ForcedIncludeFiles) true + $(DXSDK_DIR)Include\ - kernel32.lib;user32.lib;d3d9.lib;d3dx9.lib;Strmiids.lib;dinput.lib;ws2_32.lib;%(AdditionalDependencies) + d3d9.lib;d3dx9.lib;dinput.lib;Strmiids.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(ProjectName).dll %(IgnoreSpecificDefaultLibraries) exports.def @@ -179,6 +168,7 @@ MachineX86 false + $(DXSDK_DIR)lib\x86\ "$(ProjectDir)postbuild.cmd" release "$(TargetPath)" @@ -209,9 +199,10 @@ $(IntDir)/%(RelativeDir)/ stdafx.h;%(ForcedIncludeFiles) true + $(DXSDK_DIR)Include\ - kernel32.lib;user32.lib;d3d9.lib;d3dx9.lib;Strmiids.lib;dinput.lib;ws2_32.lib;%(AdditionalDependencies) + d3d9.lib;d3dx9.lib;dinput.lib;Strmiids.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(ProjectName).dll %(IgnoreSpecificDefaultLibraries) exports.def @@ -228,6 +219,7 @@ MachineX86 false + $(DXSDK_DIR)lib\x86\ "$(ProjectDir)postbuild.cmd" releasexp "$(TargetPath)" @@ -252,22 +244,22 @@ false Use Level3 - ProgramDatabase + OldStyle StdCall $(IntDir)/%(RelativeDir)/ $(IntDir)/%(RelativeDir)/ stdafx.h;%(ForcedIncludeFiles) - true false - AssemblyAndSourceCode + true + $(DXSDK_DIR)Include\ - kernel32.lib;user32.lib;d3d9.lib;d3dx9.lib;Strmiids.lib;dinput.lib;ws2_32.lib;%(AdditionalDependencies) + d3d9.lib;d3dx9.lib;dinput.lib;Strmiids.lib;ws2_32.lib;%(AdditionalDependencies) $(OutDir)$(ProjectName).dll %(IgnoreSpecificDefaultLibraries) exports.def ws2_32.dll;d3d9.dll;d3dx9_43.dll;%(DelayLoadDLLs) - false + true Console true true @@ -279,12 +271,14 @@ MachineX86 false + $(DXSDK_DIR)lib\x86\ "$(ProjectDir)postbuild.cmd" devxp "$(TargetPath)" + @@ -296,6 +290,7 @@ + @@ -311,7 +306,7 @@ - + @@ -345,7 +340,7 @@ - + @@ -383,10 +378,12 @@ + + @@ -400,7 +397,7 @@ - + @@ -415,7 +412,7 @@ - + @@ -477,7 +474,6 @@ - diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index 2e04296f..1a87a52a 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -22,7 +22,7 @@ Modules - + Modules @@ -37,9 +37,6 @@ Modules - - Modules - Modules @@ -284,13 +281,18 @@ Modules + + Modules + + + Modules - + Modules @@ -305,9 +307,6 @@ Modules - - Modules - Modules @@ -528,13 +527,17 @@ Modules + + Modules + + + - FalloutEngine diff --git a/sfall/ddraw_CheckAddr.ini b/sfall/ddraw_CheckAddr.ini new file mode 100644 index 00000000..adc11e01 --- /dev/null +++ b/sfall/ddraw_CheckAddr.ini @@ -0,0 +1,806 @@ +;sfall configuration settings - version for checking conflicting addresses. + +[Main] +;Change to 1 if you want to use command line args to tell sfall to use another ini file. +UseCommandLine=0 + +;Uncomment and point to a file to get alternate translations for some sfall messages +;TranslationsINI=.\Translations.ini + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[ExtraPatches] +;This section allows you to set multiple paths to folders containing mods or patches that will be loaded by the game +;The priority of read files will be higher than the files in patchXXX.dat +;If DataLoadOrderPatch is enabled, the data load order will be: +;master_patches > critter_patches > PatchFile99 - PatchFile0 > patchXXX.dat > ... +;Paths to folders and Fallout .dat files are supported. The available range for PatchFile option names is from 0 to 99 +;The greater numbers take precedence over lesser numbers +;PatchFile0=mods\RP_data + +;Path to the folder in which the game will automatically search and load custom .dat files +;The data load order of the .dat files in the folder will be in reverse alphabetical order of the filenames +;The .dat files specified in the PatchFileXX options will have higher priority than others +;Uncomment the option to specify a different folder path. The default path is \mods\ +;AutoSearchPath=mods + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Sound] +;Sets the number of allowed simultaneous sound effects +;Set to 0 to leave the default unchanged (i.e. 4) +NumSoundBuffers=16 + +;Set to 1 to allow attaching sound files to combat float messages +AllowSoundForFloats=1 + +;Set to 1 to automatically search for alternative formats (mp3/wma/wav) when Fallout tries to play an acm +;Set to 2 to play alternative music files even if original acm files are not present in the music folder +;This does not effect the play_sfall_sound and stop_sfall_sound script functions +AllowDShowSound=2 + +;Set to 1 to override the music path used by default (i.e. data\sound\music\) if not present in the cfg +;Set to 2 to overwrite all occurances of the music path +OverrideMusicDir=2 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Speed] +;Set to 0 to disable everything in this section +Enable=1 + +;The speeds corresponding to each slot in percent. (i.e. 100 is normal speed) +SpeedMulti0=50 +SpeedMulti1=100 +SpeedMulti2=200 +SpeedMulti3=300 +SpeedMulti4=400 +SpeedMulti5=500 +SpeedMulti6=600 +SpeedMulti7=700 +SpeedMulti8=800 +SpeedMulti9=900 + +;The initial speed at game startup +SpeedMultiInitial=110 + +;Set to 1 to also affect the playback speed of mve video files without an audio track +AffectPlayback=1 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Graphics] +;Set to 0 for 8 bit fullscreen +;Set to 4 for DX9 fullscreen +;Set to 5 for DX9 windowed +;A DX9 mode is required for any graphics related script extender functions to work (i.e. fullscreen shaders) +;Modes 1, 2 and 3 are no longer supported +Mode=5 + +;If using a DX9 mode, this changes the resolution +;The graphics are simply stretched to fit the new window; this does _not_ let you see more of the map +;If set to 0, use Fallout's native resolution +GraphicsWidth=1280 +GraphicsHeight=720 + +;Set to 1 to do the palette conversion on the GPU +;Set to 2 to do the palette conversion on the CPU +;Set to 0 to pick automatically +;GPU is faster, but requires v2.0 pixel shader support +GPUBlt=0 + +;Set to 1 to allow using 32-bit textures for talking heads +;The texture files should be placed in art\heads\\ (w/o extension) +;The files in the folder should be numbered according to the number of frames in the talking head FRM file (0.png, 1.png, etc.) +;See the text file in the modders pack for a detailed description +;Requires DX9 graphics mode and v2.0 pixel shader support (see GPUBlt option) +Use32BitHeadGraphics=1 + +;Set to 1 to automatically search for alternative avi video files when Fallout tries to play the game movies +;Requires DX9 graphics mode 4 or 5 +AllowDShowMovies=1 + +;Fade effect time percentage modifier +;Default is 100. Decrease/increase this value to speed up/slow down fade effects +FadeMultiplier=101 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Interface] +;Set to 1 to use the expanded world map interface +;Set to 2 to skip correcting the position of entrance markers on town maps +;You can use resized FRMs in 700x682 for town maps in the expanded world map interface +;Requires High Resolution Patch v4.1.8 and a new WORLDMAP.frm file in art\intrface\ (included in sfall.dat) +;The resolution of hi-res patch must be set to at least 890x720 +ExpandWorldMap=1 + +;Set to 1 to expand the number of action points displayed on the interface bar +;Requires new IFACE_E.frm and HR_IFACE_E.frm files in art\intrface\ (included in sfall.dat) to display correctly +;The minimum supported version of High Resolution Patch is 4.1.8 +ActionPointsBar=1 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Input] +;Set to 1 to enable the mouse scroll wheel to scroll through the inventory, barter, and loot screens +UseScrollWheel=1 + +;The mouse Z position is divided by this modifier to calculate the number of inventory +;slots to scroll. My mouse moves 120 pixel in the z direction for one click of the mouse +;wheel, but this may vary depending on your mouse manufacturer and windows settings. +;Set to 0 to only ever scroll 1 click +ScrollMod=0 + +;Adjusts mouse sensitivity to some percentage of normal. +;Negative values are valid if you want your mouse axis reversed for some reason +MouseSensitivity=100 + +;DX scancode of a key to press when the middle mouse button is clicked +;The default of 0x30 toggles between your two weapons +;Set to 0 to disable +MiddleMouse=0x30 + +;Set to 1 to reverse the left and right mouse buttons +ReverseMouseButtons=0 + +;Set these to 1 if you want Fallout to access the keyboard or mouse in background mode +;Try these if you get the 'Failure initializing input devices' error +BackgroundKeyboard=0 +BackgroundMouse=0 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +;The modifier key you have to hold down to change any speed settings +;Set to 0 if you don't want to use a modifier key, or a DX scancode otherwise +;Set to -1 for either ctrl key, -2 for either alt key or -3 for either shift key +SpeedModKey=-1 + +;A key to press to toggle the speed tweak on or off +;Specify 0 if you don't want a toggle key, or a DX scancode otherwise +SpeedToggleKey=0 + +;The keys corresponding to the 10 speed slots +;Set to 0 to disable a slot, otherwise specify the DX scancode of the key you want to use +SpeedKey0=0x52 +SpeedKey1=0x4f +SpeedKey2=0x50 +SpeedKey3=0x51 +SpeedKey4=0x4b +SpeedKey5=0x4c +SpeedKey6=0x4d +SpeedKey7=0x00 +SpeedKey8=0x00 +SpeedKey9=0x00 + +;A key to hold down to move the window around when using graphics mode 5 +;Set to 0 if you don't want to use a modifier key, or a DX scancode otherwise +;Set to -1 for either ctrl key, -2 for either alt key or -3 for either shift key +WindowScrollKey=0 + +;A key to press to reload your currently equipped weapon +;Set to 0 if you don't want a reload key, or a DX scancode otherwise +ReloadWeaponKey=0 + +;A key to hold down to let you move/drop a whole stack of items at once without the 'Move Items' window +;Set to 0 if you don't want to use a modifier key, or a DX scancode otherwise +ItemFastMoveKey=29 + +;Set to 1 to skip the 'Move Items' window when taking items from containers or corpses and not holding down ItemFastMoveKey +;Requires ItemFastMoveKey to be enabled +FastMoveFromContainer=0 + +;A key to press to open a debug game editor +;Set to 0 to disable, or a DX scancode otherwise +;Requires sfall debugging mode and FalloutClient.exe from the modders pack +DebugEditorKey=0 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Misc] +;Changes some of Fallout 2 engine functions to Fallout 1 behavior: +;- disables playing the final movie/credits after the endgame slideshow +;- disables halving the weight for power armor items +Fallout1Behavior=1 + +;Time limit in years. Must be between -3 and 13 +;Set to 0 if you want to die the instant you leave arroyo +;Set to -1 to remove the time limit, and automatically reset the date back to 2241 each time you would have reached it +;Set to -2 or -3 to remove the time limit, automatically reset the date, but override Fallout's GetDate function to return the correct year +TimeLimit=-1 + +;World map travel time percentage modifier +;Modifies how fast in game time passes while you're moving around the map screen without affecting encounter rate +;100 is normal speed, 0 stops time from passing +WorldMapTimeMod=100 + +;Set to 1 to use the Fallout 1 code to control world map speed +WorldMapFPSPatch=1 + +;Controls the world map speed if WorldMapFPSPatch is 1. Higher values cause slower movement +WorldMapDelay2=66 + +;Set to 1 to enable Ray's patch to make world map encounter rate independent of your travel speed +;Higher values of WorldMapEncounterRate cause a slower encounter rate +WorldMapEncounterFix=1 +WorldMapEncounterRate=5 + +;The number of slots available in the locations list panel of the world map +;Set to 0 to leave unchanged. 17 is default. +;Setting this greater than 17 requires a replacement background FRM, or you'll get glitched graphics +WorldMapSlots=17 + +;To start a new game somewhere other than artemple.map, uncomment the next line and set it to the map you want to load +;StartingMap= + +;To change the 'FALLOUT II v1.02d' version string on the main menu, uncomment the next line +;You can use up to 2 %d's in this if you want to include Fallout's version number somewhere +VersionString=Test + +;To use a config file other than fallout2.cfg, uncomment the next line and add the name of your new file +;ConfigFile= + +;Set to 1 to enable functions relating to overriding the file system +UseFileSystemOverride=1 + +;To use a patch file other than patch000.dat, uncomment the next line and add your new file name +;If you want to load multiple patch files (up to 1000) at once, you can include a %d in the file name (sprintf syntax) +PatchFile=patch%03d.dat + +;Set to 1 to use the modified data load order for the engine to find game data +;Original: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat +;Modified: master_patches > critter_patches > patchXXX.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat +DataLoadOrderPatch=1 + +;Set to 1 to enable loading alternative dialog msg files from text\\dialog_female\ for female PC +;Set to 2 to also load subtitle files from text\\cuts_female\ for female PC +FemaleDialogMsgs=1 + +;To change the default and starting player models, uncomment the next four lines. +;The default models can also be changed ingame via script +;MaleStartModel=hmwarr +;MaleDefaultModel=hmjmps +;FemaleStartModel=hfprim +;FemaleDefaultModel=hfjmps + +;To change the various ingame movies, modify the next 17 lines +;Most of these can also be changed ingame via script. +Movie1=iplogo.mve +Movie2=intro.mve +Movie3=elder.mve +Movie4=vsuit.mve +Movie5=afailed.mve +Movie6=adestroy.mve +Movie7=car.mve +Movie8=cartucci.mve +Movie9=timeout.mve +Movie10=tanker.mve +Movie11=enclave.mve +Movie12=derrick.mve +Movie13=artimer1.mve +Movie14=artimer2.mve +Movie15=artimer3.mve +Movie16=artimer4.mve +Movie17=credits.mve + +;To change the starting year, month or day, uncomment the next 3 lines +;Both StartMonth and StartDay are 0-indexed (i.e. 0 is January or the first day of a month) +StartYear=2241 +StartMonth=6 +StartDay=25 + +;To change the limit of the distance away from the player to which you're allowed to scroll the local maps, uncomment the next two lines +;Defaults are 480 in the x direction and 400 in the y direction. +;Not compatible with the res patch! +LocalMapXLimit=480 +LocalMapYLimit=400 + +;To add additional perks to the game, uncomment the next line and set it to point to a file containing perk information +;PerksFile=Perks.ini + +;Set to 1 if you want the pipboy to be available at the start of the game +;Set to 2 to make the pipboy available by only skipping the vault suit movie check +PipBoyAvailableAtGameStart=2 + +;Set to 1 to double the number of available kill types +;If you use this, you need to provide the extra descriptions in proto.msg (entries from 1450 to 1487 are names and 1488 to 1525 are descriptions) +;Changing this option mid game will not invalidate old save games, but the player's kill counters will be completely screwed. +;Setting this to 1 may cause problems if the player kills more than 32767 of any one critter type +;Additional kill types must have their new critical tables set! (See the OverrideCriticalTable option) +ExtraKillTypes=1 + +;Choose the damage formula used to calculate combat damage. +;Don't set this to anything other than 0 unless another mod you're using explicitly tells you to! +;0 - Fallout default +;1 - Glovz's Damage Fix +;2 - Glovz's Damage Fix with Damage Multiplier tweak +;5 - Haenlomal's Yet Another Ammo Mod. +DamageFormula=0 + +;Prevents you from using 0 to escape from dialogue at any time. +DialogueFix=1 + +;Prevents you from using number keys to enter unvisited areas on a town map +TownMapHotkeysFix=1 + +;Set to 1 to disable the horrigan encounter +DisableHorrigan=1 + +;Set to 1 to disable the random element in NPC levelling. +;This will cause all NPC party members to automatically level up as soon as the player reaches the requirements +NPCAutoLevel=1 + +;Change the initial starting location and world map viewport +;Leave at -1 for default +StartXPos=200 +StartYPos=200 +ViewXPos=200 +ViewYPos=200 + +;Set to 1 to force Fallout not to use multiple processor cores even if they are available +SingleCore=0 + +;Set to 1 to override the art_chache_size setting in fallout2.cfg +OverrideArtCacheSize=1 + +;Prevents you from saving in combat except at the start of your turn to avoid a few bugs +;Note that even with this option enabled, it is still not advisable to save in combat at all. +;Set to 2 to block all saving in combat +SaveInCombatFix=1 + +;Point to an ini file containing elevator data +ElevatorsFile=nofile.ini + +;Uncomment and set a comma delimited list of numbers to use a custom xp table. +;Player's level is capped once the highest specified level is reached +XPTable=50,100,200 + +;Set to 1 to enable additional weapon animation codes from o-t +;The 4 byte value at 0x39 of weapon protos may range from 0 to 15 rather than 0 to 10 +;Since the letters 'n' and 'r' are in use for other animations, an animation code of 11 corrisponds to 's' and 15 to 't' +AdditionalWeaponAnims=1 + +;Uncomment these lines to modify the default modifiers for aimed shots at specific bodyparts +;Modifiers affect both NPCs and the player +;BodyHit_Head=-40 +;BodyHit_Left_Arm=-30 +;BodyHit_Right_Arm=-30 +;BodyHit_Torso=0 +;BodyHit_Right_Leg=-20 +;BodyHit_Left_Leg=-20 +;BodyHit_Eyes=-60 +;BodyHit_Groin=-30 +;The modifier for unaimed shots +;BodyHit_Torso_Uncalled=0 + +;Set to 1 to use a CriticalOverrides.ini file to override the default critical table +;Set to 2 to use the default critical with bug fixes (doesn't require an ini) +;Set to 3 to use a new format CriticalOverrides.ini file, with preadded bug fixes +;If the ExtraKillTypes option is enabled, this should be set to 3, with containing entries for any new types +;Must be non-zero to use the edit/get/reset_critical script functions +OverrideCriticalTable=2 +;To change the path and filename of the critical table file, uncomment the next line +;OverrideCriticalFile=CriticalOverrides.ini + +;Set to 1 to get notification of karma changes in the notification window +DisplayKarmaChanges=0 + +;Set to 1 to always reload messages, rather than only at map load +AlwaysReloadMsgs=1 + +;Set to 1 to force the player to play the idle animation when reloading their weapon +PlayIdleAnimOnReload=1 + +;Set to 1 to prevent corpses from blocking line of fire +CorpseLineOfFireFix=1 + +;Changes the timer (in days) for deleting corpses on a map after you leave (valid range: 0..13) +;The corpses of critters with 'Ages' flag or on maps with 'dead_bodies_age=No' set in maps.txt will not disappear +;Default is 6. Set to 0 for a 12-hour timer +CorpseDeleteTime=1 + +;Set a number of milliseconds to idle each input loop +;Set to -1 to disable +;Set to 0 to idle only if other processes are waiting for processor time +;Set to 1 (or some higher number if needed, the maximum is 127) to prevent 100% CPU use +ProcessorIdle=1 + +;Set to 1 if using the hero appearance mod +;Set to 2 for backward compatibility with scripts that manually fix obj_art_fid/art_change_fid_num script functions for dude_obj +;You can add AppChCrt.frm and AppChEdt.frm files to art\intrface\ to set a custom background for the character screen +EnableHeroAppearanceMod=1 + +;Set to 1 to skip the 3 opening movies +;Set to 2 to also skip the splash screen +SkipOpeningMovies=2 + +;Causes NPCs who complete their combat turn with AP left over will try and find other ways to spend it. +;Only NPCs with AP left equal to or greater than the value given here will be considered. +;Set to 0 to disable +NPCsTryToSpendExtraAP=3 + +;Set to 1 to fix NPCs not checking weapon perks properly when choosing the best weapon in combat +;Set to 2 to change the priority multiplier for having weapon perk to 3x (the original is 5x) +;Note that enabling this option can significantly affect the weapon of choice for some NPCs +AIBestWeaponFix=2 + +;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as drug use preference when using drugs in their inventory +AIDrugUsePerfFix=1 + +;Allows the use of tiles over 80x36 in size. sfall will just split and resave them at startup +;Set to 1 to check all tiles on started (slow) +;Set to 2 if you provide a XLtiles.lst file in art\tiles\ containing a list of the tile ids that need checking +AllowLargeTiles=1 + +;Set to 1 to boost the maximum number of tile FRMs from 4096 to 16383 +MoreTiles=1 + +;Change the Skilldex cursor FRM numbers +;Default is 293 for all skills +Lockpick=292 +Steal=292 +Traps=292 +FirstAid=292 +Doctor=292 +Science=292 +Repair=292 + +;Set to 1 to add scroll buttons to the pipboy quest list, and remove the quests per area limit +;Set to 2 to use a different set of scroll buttons +UseScrollingQuestsList=1 + +;To change the location of quest list scroll buttons, uncomment the next two lines +;Defaults are 140 for the X-axis and 334 for the Y-axis +;QuestsScrollButtonsX=140 +;QuestsScrollButtonsY=334 + +;Uncomment these lines to control the premade characters offered when starting a new game +;Multiple options should be separated by commas, and there must be the same number of entries in both lines +;Each name in PremadePaths is limited to 11 characters +PremadePaths=combat,diplomat,stealth +PremadeFIDs=201,203,202 + +;Use this line to modify the list of cities and their associated global variables used for city reputations +;Syntax is 'city id:global id', with each city/global pair separated by a comma. +CityRepsList=0:47,2:48,1:49,4:50,5:51,3:52,8:53,6:54,7:55,13:56,10:57,11:59,14:61,17:63,19:64,18:65,25:66,9:294,20:308 + +;Set this to a valid path to save a copy of the console contents +ConsoleOutputPath="bingle.txt" + +;Set to 1 to add additional pages of save slots +ExtraSaveSlots=1 + +;To use more than one save slot for quick saving (F6 key) without picking a slot beforehand, set the next two lines +;AutoQuickSave sets how many save slots on a page you want to use for quick saving (valid range: 1..10) +;AutoQuickSavePage is the page number to use (valid range: 1..1000) if ExtraSaveSlots is enabled +;The quick saves will be rotated from the first slot on the page to the n-th slot +;Set to 0 to disable. AutoQuickSave will use the current selected page if AutoQuickSavePage is disabled +AutoQuickSave=1 +AutoQuickSavePage=1 + +;Set to 1 to speed up the HP/AC counter animations +;Set to 2 to update the HP/AC counters instantly +;Set to 3 to update the AC counter instantly when switching to other controlled critters in combat +SpeedInterfaceCounterAnims=1 + +;These lines allow you to control the karma FRMs displayed on the character screen +;Number of KarmaPoints should be 1 less than number of KarmaFRMs +KarmaFRMs=47,48,49 +;KarmaPoints=-100,100 + +;Set to 1 to allow science and repair to be used on the player, or 2 for all critters. (Rather than only brahmin/robots) +ScienceOnCritters=1 + +;Modify this value to change the player's speed of rotation on the inventory and character screens +;Default is 166 +SpeedInventoryPCRotation=220 + +;Modify the number of the extra interface boxes available to modders. (Default is 5, and the maximum is 95) +BoxBarCount=15 + +;Uncomment to set the text colour of the extra interface boxes +;The line must contain the same number of digits as the value of BoxBarCount, each either a 0 for green or 1 for red +;BoxBarColours=00000 + +;Set to 1 to fix the bug that caused bonus HtH damage to not be applied correctly. +BonusHtHDamageFix=1 + +;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory +DisplayBonusDamage=0 + +;Set to 1 to display the range of the second attack mode in the inventory when you switch weapon modes in active item slots +DisplaySecondWeaponRange=1 + +;Modify the maximum number of animations allowed to run on a map. (Default is 32, and the maximum is 127) +AnimationsAtOnceLimit=120 + +;Set to 1 to remove the limits that stop the player rolling critical successes/misses in the first few days of game time +RemoveCriticalTimelimits=1 + +;Change the colour of the font used on the main menu for the Fallout/sfall version number and copyright text +;It's the last byte ('3C' by default) that picks the colour used. The first byte supplies additional flags for this option +;MainMenuFontColour=0x00003C +;Change the colour of the font used on the main menu for the button text +MainMenuBigFontColour=0x3C + +;Two alternate fixes to the interaction between HtH attacks and the fast shot trait +;0 - Fallout 2 original behaviour +;1 - Haenlomal's fix, called shots are enabled for HtH attacks +;2 - Restoring the -1 AP bonus for HtH attacks (i.e. Fallout 1 behaviour) +FastShotFix=2 + +;Set to 1 to boost the maximum number of script names from 1450 to 10000 +BoostScriptDialogLimit=1 + +;Allows you to edit the skill tables +;Point the next line to an ini file containing the replacement skill data +;SkillsFile=Skills.ini + +;To change the relationship between SPECIAL stats and derived stats, uncomment the next line +;See the Stats.ini in the modders pack for an example file +;DerivedStats=Stats.ini + +;Allows you to change some parameters for drugs and their addictions +;See the Drugs.ini in the modders pack for an example file +;DrugsFile=Drugs.ini + +;These options modify the checks to see if a critter can carry an additional item, changing which items are counted towards the weight limit and adding an additional size check +;Set the mode to 0 to disable the size check, 1 to apply to the PC only, 2 to apply to the PC and party members, or 3 to apply to all critters +;Only the PC uses CritterInvSizeLimit. Other critters will use the unused stat (STAT_unused = 10) or have the size limit of 100 if the stat is not set +;Add 4 to the mode to limit the weight check to used items only +;You can use line number 542/543 in proto.msg and line number 35 in inventry.msg to set up custom messages for item size +CritterInvSizeLimitMode=6 +CritterInvSizeLimit=200 + +;Some bit flags to alter behaviour of the motion sensor +;1 - Allow sensor use on automap when motion sensor is in pack rather than hands +;2 - Motion sensor doesn't require charges +MotionScannerFlags=3 + +;Set to non-0 to adjust the maximum encounter table size +;Default is 40, and the maximum is 127 +EncounterTableSize=127 + +;Set to 1 to disable the pipboy alarm button +DisablePipboyAlarm=1 + +;Uncomment the next four lines to move the main menu buttons and credit text (the 'Copyright(c)' line on the main menu) +MainMenuOffsetX=1 +MainMenuOffsetY=1 +;MainMenuCreditsOffsetX=0 +;MainMenuCreditsOffsetY=0 + +;Prevents you from using super stims on a critter who is at full health. +SuperStimExploitFix=1 + +;Change the AP cost for accessing inventory in combat, and the related effect of Quick Pockets perk +InventoryApCost=3 +QuickPocketsApCostReduction=2 + +;Set to 1 to allow objects seeing through other objects that have their ShootThru flag set +;Note that enabling this option can cause unexpected NPC behavior in some situations +ObjCanSeeObj_ShootThru_Fix=1 + +;Set to 1 to fix the broken obj_can_hear_obj script function +ObjCanHearObjFix=1 + +;Set to 1 to enable the mood argument of start_gdialog script function to be available for talking heads +;If the argument value is -1, the mood will be determined by the local variable 0 of the script (vanilla behavior) +StartGDialogFix=1 + +;Set to 1 to fix and repurpose the unused called_shot/num_attacks arguments of attack_complex script function +;This also changes the behavior of the result flags arguments +;called_shot - additional damage, when the damage received by the target is above the specified minimum damage +;num_attacks - the number of free action points on the first turn only +;attacker_results - unused, must be 0 or not equal to the target_results argument when specifying result flags for the target +AttackComplexFix=1 + +;Set to 1 to enable the balanced bullet distribution formula for burst attacks +ComputeSprayMod=1 + +;These options modify the bullet distribution of burst attacks if ComputeSprayMod is 1 +;All the bullets are divided into three groups: central, left and right +;These three groups will then travel along three parallel tracks, trying to hit targets on the way +;CenterMult/Div set the ratio of how many bullets go to the central group, and remaining bullets are divided equally to left and right sides +;TargetMult/Div set the ratio of how many bullets in the central group will attack the primary target directly +;Multipliers are capped at divisor values +ComputeSpray_CenterMult=1 +ComputeSpray_CenterDiv=3 +ComputeSpray_TargetMult=1 +ComputeSpray_TargetDiv=2 + +;Set to 1 to make explosions and projectiles emit light +ExplosionsEmitLight=1 + +;Uncomment these lines to change explosives damage. DmgMax can be set to 9999 at max, and DmgMin is capped at DmgMax +Dynamite_DmgMax=51 +Dynamite_DmgMin=31 +PlasticExplosive_DmgMax=81 +PlasticExplosive_DmgMin=41 + +;Uncomment these lines to change the timers of how many days after the game starts Hakunin dream sequences will occur +MovieTimer_artimer1=91 +MovieTimer_artimer2=181 +MovieTimer_artimer3=271 +MovieTimer_artimer4=359 + +;Set to 1 to enable the new arrays behavior +;Set to 0 for backward compatibility with pre-3.4 scripts +arraysBehavior=1 + +;Set to 1 to add proper checks if there is enough ammo to use weapons that use multiple ammo per shot +CheckWeaponAmmoCost=1 + +;To add additional books to the game, uncomment the next line and point to a file containing book information +;See the Books.ini in the modders pack for an example file +;BooksFile=Books.ini + +;Controls the speed of combat panel animations (lower - faster; valid range: 0..65535) +CombatPanelAnimDelay=500 +;Controls the speed of dialog panel animations (lower - faster; valid range: 0..255) +DialogPanelAnimDelay=16 +;Controls the speed of pipboy alarm clock animations (lower - faster; valid range: 0..127) +PipboyTimeAnimDelay=25 + +;Set to 1 to stack empty identical weapons, no matter what type of ammo was loaded before +StackEmptyWeapons=1 + +;Changes the way weapon reloading works when you drag ammo onto a weapon in the inventory +;Set to -1 to disable (vanilla behavior with the 'Move Items' window) +;Set to 0 to use all the ammo boxes to reload +;Set to 1 or more to reserve the specified amount of ammo boxes and use all the rest to reload +;If the amount of ammo boxes in the inventory is less than or equal to the reserve, only one box will be used +ReloadReserve=1 + +;Set to 1 to change the counter in the 'Move Items' window to start with maximum number, except in the barter screen +ItemCounterDefaultMax=1 + +;Set to 1 to leave the music playing in dialogue with talking heads +EnableMusicInDialogue=1 + +;Set to 1 to prevent the player from running while sneaking without Silent Running perk +DontTurnOffSneakIfYouRun=1 + +;Changes the distance at which the player will switch to walking when trying to use objects (valid range: 0..3) +;Set to 0 to disable switching. (Default is 3) +UseWalkDistance=2 + +;Set to 1 to fix the bug of being unable to sell used geiger counters or stealth boys +CanSellUsedGeiger=1 + +;Set to 1 to fix the issue with being able to charge the car by using cells on other scenery/critters +;Set to 0 if another mod you're using has custom vehicles +CarChargingFix=1 + +;Set to 1 to prevent the car from being lost when entering a location via the Town/World button and then leaving on foot +;Note that the global variable 633 (GVAR_CAR_PLACED_TILE) will be set to -1 when the player leaves a location +CarPlacedTileFix=1 + +;Set to 1 to skip weapon equip/unequip animations when performing various actions +InstantWeaponEquip=1 + +;To add additional game msg files, uncomment the next line and set a comma delimited list of filenames without .msg extension +;By default, the files will have consecutive numbers assigned beginning with 0 +;You can use the syntax 'filename:number' to manually assign numbers to specific msg files, with each pair separated by a comma +;If a file after the specified pair does not have a number assigned, it will have the next consecutive number from the last pair +;You need to use the message_str_game script function to get messages from the files +;ExtraGameMsgFileList= + +;Set to 1 to display numbered dialogue options +NumbersInDialogue=1 + +;Set to 1 to use Fallout's normal text font instead of DOS-like font on the world map +WorldMapFontPatch=1 + +;Set to 1 to keep the selected attack mode when moving the weapon between active item slots +KeepWeaponSelectMode=1 + +;Set to 1 to display full item description for weapon/ammo in the barter screen +FullItemDescInBarter=1 + +;Set to 1 to display experience points with the bonus from Swift Learner perk when gained from non-scripted situations +DisplaySwiftLearnerExp=1 + +;Set to 1 to display party member's current level/AC/addict flag on the combat control panel +PartyMemberExtraInfo=1 + +;Set to 1 to fix the bug of using First Aid/Doctor skills when using them on the player +;This will cause the party member to apply his/her skills when you use First Aid/Doctor skills on the player, but only if +;the player is standing next to the party member +;Note that because the related engine function is not fully implemented, enabling this option without a global script +;that overrides First Aid/Doctor functions has very limited usefulness +PartyMemberSkillFix=1 + +;Set to 1 to skip loading all game settings except the game/combat difficulty from a saved game +;Set to 2 to also skip loading the game/combat difficulty settings +SkipLoadingGameSettings=2 + +;Set to 1 to prevent the inventory/loot/automap interfaces from being placed on top of other script-created windows +InterfaceDontMoveOnTop=1 + +;Overrides the global variable number used to show the special death message of the Modoc toilet explosion +;Set to -1 to disable the special death message when the global variable is set +SpecialDeathGVAR=-1 + +;Set to 1 to disable the special handling of map IDs 19 and 37 in the engine when entering the maps +;Note that enabling this option can break the map changes in Modoc and Vault 15 +DisableSpecialMapIDs=1 + +;Changes the base value of the duration of the knockout effect (valid range: 35..100; default is 35) +;The formula for the duration in ticks is: 10 * (value - 3 * EN) +KnockoutTime=36 + +;Set to 1 to display sfall built-in credits at the bottom of credits.txt contents instead of at the top +CreditsAtBottom=0 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Scripts] +;Comma-separated list of masked paths to load global scripts from +;Only use single backslash \ as the directory separator +;Paths outside of scripts folder are supported +GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int + +;Uncomment the option to specify a common folder path for all ini files used by scripts +;You will have to put all the available ini files of the mods in this directory +;The maximum length of the specified path is 61 characters +;IniConfigFolder=IniConfig + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Debugging] +;Extra sfall configuration settings that can be used by modders +;This section is not effected by the 'UseCommandLine' option. It will always be read from the main ini + +;Set to 1 to enable sfall debugging mode +Enable=11 + +;Fallout 2 Debug Patch +;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and debug.log +;Does not require sfall debugging mode +;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg: +;------- +;[debug] +;mode=environment +;output_map_data_info=1 +;show_load_info=1 +;show_script_messages=1 +;show_tile_num=1 +;[sound] +;debug=1 +;debug_sfxc=1 +;------- +DebugMode=3 + +;Set to 1 to hide error messages in debug output when a null value is passed to the function as an object +HideObjIsNullMsg=1 + +;Change to 1 to skip the compatibility mode check +SkipCompatModeCheck=0 + +;Set to 1 to skip the executable file size check +;Does not require sfall debugging mode +SkipSizeCheck=0 + +;If you're testing changes to the Fallout exe, you can override the CRC that sfall looks for here +;You can use several hex values, separated by commas +;Does not require sfall debugging mode +;ExtraCRC=0x00000000,0x00000000 + +;Set to 1 to stop Fallout from deleting non read-only protos at startup +;Has pretty nasty side effects when saving/reloading, so don't use for regular gameplay +DontDeleteProtos=0 + +;Set to 1 to give scripts direct access to Fallout's address space, and to make arbitrary calls into Fallout's code +;Does not require sfall debugging mode +AllowUnsafeScripting=0 + +;Set to 1 to force sfall to inject all hooks code into the game, even if corresponding hook scripts don't exist +InjectAllGameHooks=1 + +;Set to 1 to force sfall to search for global scripts every time the game loads rather than only once on the first game start +AlwaysFindScripts=0 + +;Set to 1 to force critters to display combat float messages +;Requires AllowSoundForFloats to be enabled +Test_ForceFloats=1 + +;These options control what output is saved in the debug log (sfall-log.txt) +;Prints messages duing sfall initialization +Init=1 +;Prints messages relating to hook scripts +Hook=0 +;Prints messages relating to scripting +Script=0 +;Prints messages relating to the critical table +Criticals=0 diff --git a/sfall/main.cpp b/sfall/main.cpp index fbe41088..0ff293ee 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -24,16 +24,16 @@ #include "ModuleManager.h" #include "Modules\Module.h" #include "Modules\AI.h" -#include "Modules\AnimationsAtOnceLimit.h" +#include "Modules\Animations.h" #include "Modules\BarBoxes.h" #include "Modules\Books.h" #include "Modules\BugFixes.h" #include "Modules\BurstMods.h" #include "Modules\Combat.h" #include "Modules\Console.h" -#include "Modules\CRC.h" #include "Modules\Credits.h" #include "Modules\Criticals.h" +#include "Modules\CritterStats.h" #include "Modules\DamageMod.h" #include "Modules\DebugEditor.h" #include "Modules\Drugs.h" @@ -72,8 +72,9 @@ #include "Modules\TalkingHeads.h" #include "Modules\Tiles.h" #include "Modules\Worldmap.h" -#include "SimplePatch.h" +#include "CRC.h" +#include "SimplePatch.h" #include "Logging.h" #include "Utils.h" #include "Version.h" @@ -101,16 +102,20 @@ DWORD HRPAddressOffset(DWORD offset) { return (hrpDLLBaseAddr + offset); } -unsigned int GetConfigInt(const char* section, const char* setting, int defaultValue) { - return GetPrivateProfileIntA(section, setting, defaultValue, ini); +int iniGetInt(const char* section, const char* setting, int defaultValue, const char* iniFile) { + return GetPrivateProfileIntA(section, setting, defaultValue, iniFile); +} + +size_t iniGetString(const char* section, const char* setting, const char* defaultValue, char* buf, size_t bufSize, const char* iniFile) { + return GetPrivateProfileStringA(section, setting, defaultValue, buf, bufSize, iniFile); } std::string GetIniString(const char* section, const char* setting, const char* defaultValue, size_t bufSize, const char* iniFile) { char* buf = new char[bufSize]; - GetPrivateProfileStringA(section, setting, defaultValue, buf, bufSize, iniFile); + iniGetString(section, setting, defaultValue, buf, bufSize, iniFile); std::string str(buf); delete[] buf; - return trim(str); + return str; } std::vector GetIniList(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter, const char* iniFile) { @@ -119,12 +124,19 @@ std::vector GetIniList(const char* section, const char* setting, co return list; } +/* + For ddraw.ini config +*/ +unsigned int GetConfigInt(const char* section, const char* setting, int defaultValue) { + return iniGetInt(section, setting, defaultValue, ini); +} + std::string GetConfigString(const char* section, const char* setting, const char* defaultValue, size_t bufSize) { - return GetIniString(section, setting, defaultValue, bufSize, ini); + return trim(GetIniString(section, setting, defaultValue, bufSize, ini)); } size_t GetConfigString(const char* section, const char* setting, const char* defaultValue, char* buf, size_t bufSize) { - return GetPrivateProfileStringA(section, setting, defaultValue, buf, bufSize, ini); + return iniGetString(section, setting, defaultValue, buf, bufSize, ini); } std::vector GetConfigList(const char* section, const char* setting, const char* defaultValue, size_t bufSize) { @@ -136,7 +148,7 @@ std::string Translate(const char* section, const char* setting, const char* defa } size_t Translate(const char* section, const char* setting, const char* defaultValue, char* buffer, size_t bufSize) { - return GetPrivateProfileStringA(section, setting, defaultValue, buffer, bufSize, translationIni); + return iniGetString(section, setting, defaultValue, buffer, bufSize, translationIni); } static void InitModules() { @@ -146,7 +158,6 @@ static void InitModules() { // initialize all modules manager.add(); // fixes should be applied at the beginning - manager.add(); manager.add(); manager.add(); manager.add(); @@ -156,14 +167,16 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); manager.add(); manager.add(); manager.add(); - manager.add(); + manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); + manager.add(); manager.add(); manager.add(); manager.add(); @@ -185,7 +198,7 @@ static void InitModules() { // manager.add(); manager.add(); - manager.add(); + manager.add(); manager.add(); manager.add(); manager.add(); @@ -206,7 +219,7 @@ static void InitModules() { static const DWORD loadFunc = 0x4FE1D0; static void LoadHRPModule() { HMODULE dll; - __asm call loadFunc; // load HRP dll + __asm call loadFunc; // get HRP loading address __asm mov dll, eax; if (dll != NULL) hrpDLLBaseAddr = (DWORD)dll; dlog_f("Loaded f2_res.dll library at the memory address: 0x%x\n", DL_MAIN, dll); @@ -224,9 +237,9 @@ static void CompatModeCheck(HKEY root, const char* filepath, int extra) { RegCloseKey(key); MessageBoxA(0, "Fallout appears to be running in compatibility mode.\n" //, and sfall was not able to disable it.\n" - "Please check the compatibility tab of fallout2.exe, and ensure that the following settings are unchecked.\n" - "Run this program in compatibility mode for..., run in 256 colours, and run in 640x480 resolution.\n" - "If these options are disabled, click the 'change settings for all users' button and see if that enables them.", "Error", 0); + "Please check the compatibility tab of fallout2.exe, and ensure that the following settings are unchecked:\n" + "Run this program in compatibility mode for..., run in 256 colours, and run in 640x480 resolution.\n" + "If these options are disabled, click the 'change settings for all users' button and see if that enables them.", "Error", MB_TASKMODAL | MB_ICONERROR); ExitProcess(-1); } @@ -238,7 +251,7 @@ static void CompatModeCheck(HKEY root, const char* filepath, int extra) { inline void SfallInit() { // enabling debugging features - isDebug = (GetPrivateProfileIntA("Debugging", "Enable", 0, ::sfall::ddrawIni) != 0); + isDebug = (iniGetInt("Debugging", "Enable", 0, ::sfall::ddrawIni) != 0); if (isDebug) { LoggingInit(); if (!ddraw.dll) dlog("Error: Cannot load the original ddraw.dll library.\n", DL_MAIN); @@ -249,7 +262,7 @@ inline void SfallInit() { CRC(filepath); - if (!isDebug || !GetPrivateProfileIntA("Debugging", "SkipCompatModeCheck", 0, ::sfall::ddrawIni)) { + if (!isDebug || !iniGetInt("Debugging", "SkipCompatModeCheck", 0, ::sfall::ddrawIni)) { int is64bit; typedef int (_stdcall *chk64bitproc)(HANDLE, int*); HMODULE h = LoadLibrary("Kernel32.dll"); @@ -267,7 +280,7 @@ inline void SfallInit() { // ini file override bool cmdlineexists = false; char* cmdline = GetCommandLineA(); - if (GetPrivateProfileIntA("Main", "UseCommandLine", 0, ::sfall::ddrawIni)) { + if (iniGetInt("Main", "UseCommandLine", 0, ::sfall::ddrawIni)) { while (cmdline[0] == ' ') cmdline++; bool InQuote = false; int count = -1; @@ -295,13 +308,13 @@ inline void SfallInit() { CloseHandle(h); strcat_s(ini, cmdline); } else { - MessageBox(0, "You gave a command line argument to fallout, but it couldn't be matched to a file\n" \ - "Using default ddraw.ini instead", "Warning", MB_TASKMODAL); + MessageBoxA(0, "You gave a command line argument to Fallout, but it couldn't be matched to a file.\n" \ + "Using default ddraw.ini instead.", "Warning", MB_TASKMODAL | MB_ICONWARNING); goto defaultIni; } } else { defaultIni: - strcpy_s(ini, ::sfall::ddrawIni); + strcpy(&ini[2], &::sfall::ddrawIni[2]); } GetConfigString("Main", "TranslationsINI", ".\\Translations.ini", translationIni, 65); @@ -309,7 +322,6 @@ defaultIni: hrpIsEnabled = (*(DWORD*)0x4E4480 != 0x278805C7); // check if HRP is enabled if (hrpIsEnabled) { - BlockCall(0x4E4480); LoadHRPModule(); if (strncmp((const char*)HRPAddressOffset(0x39940), "4.1.8", 5) == 0) hrpVersionValid = true; } diff --git a/sfall/main.h b/sfall/main.h index bc820d3d..43ca9509 100644 --- a/sfall/main.h +++ b/sfall/main.h @@ -1,22 +1,29 @@ /* -* sfall -* Copyright (C) 2008-2016 The sfall team -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 3 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ + * sfall + * Copyright (C) 2008-2016 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once +#pragma warning(disable:4996) + +#ifdef NDEBUG +#pragma warning(disable:4414) +#endif + #include //#define WIN32_LEAN_AND_MEAN #include @@ -62,25 +69,34 @@ namespace sfall #define isDebug false #endif -// Macros for quick replacement of pushad/popad assembler opcodes +// Trap for Debugger +#define BREAKPOINT __asm int 3 + +// Macros for quick replacement of assembler opcodes pushad/popad #define pushadc __asm push eax __asm push edx __asm push ecx #define popadc __asm pop ecx __asm pop edx __asm pop eax -// Gets the integer value from Sfall configuration INI file. -unsigned int GetConfigInt(const char* section, const char* setting, int defaultValue); +// Gets the integer value from given INI file. +int iniGetInt(const char* section, const char* setting, int defaultValue, const char* iniFile); + +// Gets the string value from given INI file. +size_t iniGetString(const char* section, const char* setting, const char* defaultValue, char* buf, size_t bufSize, const char* iniFile); // Gets the string value from given INI file. std::string GetIniString(const char* section, const char* setting, const char* defaultValue, size_t bufSize, const char* iniFile); -// Loads the string value from Sfall configuration INI file into the provided buffer. -size_t GetConfigString(const char* section, const char* setting, const char* defaultValue, char* buffer, size_t bufSize = 128); - // Parses the comma-separated list setting from given INI file. std::vector GetIniList(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter, const char* iniFile); -// Gets the string value from Sfall configuration INI file. +// Gets the integer value from Sfall configuration INI file. +unsigned int GetConfigInt(const char* section, const char* setting, int defaultValue); + +// Gets the string value from Sfall configuration INI file with trim function. std::string GetConfigString(const char* section, const char* setting, const char* defaultValue, size_t bufSize = 128); +// Loads the string value from Sfall configuration INI file into the provided buffer. +size_t GetConfigString(const char* section, const char* setting, const char* defaultValue, char* buffer, size_t bufSize = 128); + // Parses the comma-separated list from the settings from Sfall configuration INI file. std::vector GetConfigList(const char* section, const char* setting, const char* defaultValue, size_t bufSize = 128); diff --git a/sfall/version.h b/sfall/version.h index 623cf8b3..e48dc61a 100644 --- a/sfall/version.h +++ b/sfall/version.h @@ -24,7 +24,7 @@ #define VERSION_MAJOR 4 #define VERSION_MINOR 2 -#define VERSION_BUILD 0 +#define VERSION_BUILD 1 #define VERSION_REV 0 -#define VERSION_STRING "4.2" +#define VERSION_STRING "4.2.1"