diff --git a/artifacts/config_files/Drugs.ini b/artifacts/config_files/Drugs.ini new file mode 100644 index 00000000..12d47595 --- /dev/null +++ b/artifacts/config_files/Drugs.ini @@ -0,0 +1,30 @@ +; Allows you to configure advanced settings for drugs. +; Maximum 50 drugs are allowed +; Note: for vanilla drugs, you can only override the values of NumEffect and AddictTime parameters + +[main] +; set to 1 to change Jet addiction to be removed after the time expires +JetWithdrawal=0 + +; total number of drugs in this file +Count=0 + +; count starts from 1 +[1] +; drug item PID +PID=0 +; set a limit on the count of first and second delayed effects when using the drug, i.e. if the queue has delayed effects from the drug, +; and their count is greater or equal to NumEffects, it is not possible to obtain new effects from using the drug +; this count is 4 for Buffout/Mentats/Psycho/Jet +; set -1 to leave this parameter unchanged +NumEffects=-1 +; the duration of the addiction effect in game minutes (i.e. 1440 = 24 game hours) +; set to 0 to use the default time (7 days for all drugs except Jet) +AddictTime=0 +; the number of the global variable from vault13.gam responsible for displaying addiction in the character screen +; set to 0 if the addiction is not required +GvarID=0 +; the index number from editor.msg to display the title of the addiction (the description uses the index number of TextID + 100) +TextID=-1 +; the line number (0-indexed) of the corresponding FRM in skilldex.lst +FrmID=-1 diff --git a/artifacts/config_files/Perks.ini b/artifacts/config_files/Perks.ini index 9d31295a..a04da032 100644 --- a/artifacts/config_files/Perks.ini +++ b/artifacts/config_files/Perks.ini @@ -8,7 +8,7 @@ ; be used to remove their hardcoded effects, and add new stat/skill effects ;Name=The name of the perk (max 63 characters) -;Desc=The description of the perk (max 1023 characters) +;Desc=The description of the perk (max 255 characters) ;Image=The line number (0-indexed) of the corresponding FRM in skilldex.lst ;Ranks=The number of perk levels ;Level=The minimum required level @@ -35,10 +35,18 @@ ;If the value is set to -99999, the variable will be ignored (similar to comment out that line) -;This is a modification to perk 119 -[119] +;You can add simple extra perks with ID numbers from 119 to 255 +; that do not require using scripting functions to change player stats, and can also be used for Armor perks +;These parameters are only for extra perks +;Stat1-Stat2 = same as Stat +;Stat1Mag-Stat2Mag = same as StatMag +;Skill3-Skill5 = The skill to be modified (or -1 for none) +;Skill3Mod-Skill5Mod = The increased/decreased value to the modified skill + +;This is a modification to vanilla perk 0 +[0] Name=Example -Desc=This is an example perk~Second line +Desc=This is an example perk description Image=72 Ranks=1 Level=1 @@ -57,6 +65,39 @@ INT=0 AGL=0 LCK=0 +;Example for creating extra perks +[119] +Name=Extra Perk +Desc=This perk can change player stats and skills +Image=72 +Ranks=1 +Level=2 +Type=0 +Stat=-1 +StatMag=0 +Skill1=-1 +Skill1Mag=0 +Skill2=-1 +Skill2Mag=0 +STR=0 +PER=0 +END=0 +CHR=0 +INT=0 +AGL=0 +LCK=0 +; for extra perks only +Stat1=-1 +Stat1Mag=0 +Stat2=-1 +Stat2Mag=0 +Skill3=-1 +Skill3Mod=0 +Skill4=-1 +Skill4Mod=0 +Skill5=-1 +Skill5Mod=0 + ;This is a modification to trait 0 [t0] NoHardcode=0 diff --git a/artifacts/config_files/sfall-mods.ini b/artifacts/config_files/sfall-mods.ini index 76970d54..d466e0fa 100644 --- a/artifacts/config_files/sfall-mods.ini +++ b/artifacts/config_files/sfall-mods.ini @@ -41,6 +41,7 @@ Mode=0 ;Choose a notification box to display the name of the controlled critter above the interface bar ;Must be between 5 and (4 + the value of BoxBarCount in ddraw.ini) +;For sfall 4.1.6+ the number for the notification box is set automatically, so just use any value ;Set to 0 to disable DisplayName=0 @@ -52,4 +53,5 @@ DisplayName=0 ;4 - dark yellow ;5 - blue ;6 - purple +;7 - dull pink DisplayNameColor=0 diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index ba694096..1ae022b7 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -1,5 +1,5 @@ ;sfall configuration settings -;v4.1.5 +;v4.1.6 [Main] ;Change to 1 if you want to use command line args to tell sfall to use another ini file. @@ -8,6 +8,14 @@ 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 +;PatchFile0=mods\RP_data + ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Sound] ;Sets the number of allowed simultaneous sound effects @@ -84,7 +92,7 @@ FadeMultiplier=100 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Input] -;Set to 1 to enable the mouse scroll wheel to scroll through inventory, barter, and loot screens +;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 @@ -158,7 +166,6 @@ DebugEditorKey=0 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Misc] - ;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 @@ -200,11 +207,8 @@ WorldMapSlots=0 UseFileSystemOverride=0 ;To use a patch file other than patch000.dat, uncomment the next line and add your new file name -;If you want to check for multiple patch files, you can include a %d in the file name (sprintf syntax) -;By default, only the first patch file found will be used. -;If you want to load multiple patch files (up to 1000) at once, uncomment the MultiPatches line and set it to 1 +;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 -;MultiPatches=0 ;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 @@ -348,6 +352,11 @@ PlayIdleAnimOnReload=0 ;Set to 1 to prevent corpses from blocking line of fire CorpseLineOfFireFix=0 +;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=6 + ;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 @@ -479,17 +488,21 @@ NPCStage6Fix=0 ;2 - Restoring the -1 AP bonus for HtH attacks (i.e. Fallout 1 behaviour) FastShotFix=1 +;Set to 1 to boost the maximum number of script names from 1450 to 10000 +BoostScriptDialogLimit=0 + ;Allows you to edit the skill tables ;Point the next line at an ini file containing the replacement skill data ;SkillsFile=Skills.ini -;Set to 1 to boost the maximum number of script names from 1450 to 10000 -BoostScriptDialogLimit=0 - ;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 @@ -640,7 +653,7 @@ WorldMapFontPatch=0 ;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 barter screen +;Set to 1 to display full item description for weapon/ammo in the barter screen FullItemDescInBarter=0 ;Set to 1 to display experience points with the bonus from Swift Learner perk when gained from non-scripted situations @@ -663,6 +676,10 @@ SkipLoadingGameSettings=0 ;Set to 1 to prevent the inventory/loot/automap interfaces from being placed on top of other script-created windows InterfaceDontMoveOnTop=0 +;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=491 + ;Set to 1 to display sfall built-in credits at the bottom of credits.txt contents instead of at the top CreditsAtBottom=0 diff --git a/artifacts/mods/gl_highlighting.int b/artifacts/mods/gl_highlighting.int index 9a40a0b8..eb66fc51 100644 Binary files a/artifacts/mods/gl_highlighting.int and b/artifacts/mods/gl_highlighting.int differ diff --git a/artifacts/mods/gl_highlighting.ssl b/artifacts/mods/gl_highlighting.ssl index 11084a19..d1915edd 100644 --- a/artifacts/mods/gl_highlighting.ssl +++ b/artifacts/mods/gl_highlighting.ssl @@ -13,7 +13,7 @@ NOTE: this script requires compiler from sfall modderspack with -s option (short circuit evaluation) - version 1.0 + version 1.1 **/ @@ -67,7 +67,7 @@ procedure KeyPressHandler begin scanCode := get_sfall_arg, scanner, charges; - if scanCode == highlightKey and not(get_game_mode bwand INTFACELOOT) then begin + if scanCode == highlightKey and not(get_game_mode bwand (INTFACELOOT bwor BARTER)) then begin if pressed then begin isHighlight := true; if motionScanner then begin @@ -106,7 +106,7 @@ procedure CombatTurnHandler begin end procedure GameModeChangeHandler begin - if isHighlight and (get_game_mode bwand INTFACELOOT) then begin + if isHighlight and (get_game_mode bwand (INTFACELOOT bwor BARTER)) then begin isHighlight := false; call ToggleHighlight(false); end diff --git a/artifacts/mods/gl_npcarmor.int b/artifacts/mods/gl_npcarmor.int index 45aa8ae4..54d07d49 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 548a2485..d0a48cbb 100644 --- a/artifacts/mods/gl_npcarmor.ssl +++ b/artifacts/mods/gl_npcarmor.ssl @@ -96,7 +96,11 @@ procedure invenwield_handler begin if (critter and item and slot == INVEN_TYPE_WORN) then begin fid := check_armor_change(critter, item, isWorn); if (fid != -1) then begin - art_change_fid_num(critter, fid); + if art_exists(fid) then begin + art_change_fid_num(critter, fid); + end else begin + debug_msg("Error: NPC Armor mod: missing FID."); + end end return; end @@ -118,7 +122,13 @@ procedure adjustfid_handler begin armor := critter_inven_obj(dude_obj, INVEN_TYPE_WORN); fid := check_armor_change(dude_obj, armor, armor != 0); if (fid != -1) then begin - set_sfall_return(fid); + variable weapAnim := get_sfall_arg bwand 0xF000; + variable newFid := fid bwand 0xFFFF0FFF bwor weapAnim; + if art_exists(newFid) then begin + set_sfall_return(newFid); + end else begin + debug_msg("Error: NPC Armor mod: missing FID."); + end end end end diff --git a/artifacts/mods/gl_partycontrol.int b/artifacts/mods/gl_partycontrol.int index 260e1320..4deb0bee 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 22ee5916..8585f91f 100644 --- a/artifacts/mods/gl_partycontrol.ssl +++ b/artifacts/mods/gl_partycontrol.ssl @@ -7,7 +7,7 @@ NOTE: this script requires compiler from sfall modderspack with -s option (short circuit evaluation) - version 1.0 + version 1.1 */ @@ -15,33 +15,52 @@ #include "..\headers\command.h" #include "main.h" +#define OBJ_DATA_LIGHT_DISTANCE (0x6C) +#define OBJ_DATA_LIGHT_INTENSITY (0x70) + variable - configSection := "CombatControl", controlMode, + noCheckArray, pidList, - displayName, - displayNameColor, - maxBoxCount, + lightInt, lightDist, npcControl, + displayName, displayNameColor, inControl := false, hasGeckoSkinning := false; +procedure AllowControl(variable pid) begin + if (noCheckArray or scan_array(pidList, pid bwand 0xFFFFFF) != -1) and (party_member_obj(pid)) then return true; + return false; +end + procedure combatturn_handler begin variable status := get_sfall_arg, critter := get_sfall_arg, - arg3 := get_sfall_arg, + //arg3 := get_sfall_arg, pid; - // display_msg("Combat Turn: " + status + ", by " + obj_name(critter) + ", arg3: " + arg3); + //display_msg("Combat Turn: " + status + ", by " + obj_name(critter) + ", arg3: " + arg3); + 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/off light for NPC + end + pid := obj_pid(critter); - if (status == 1 - and (len_array(pidList) == 0 or scan_array(pidList, pid bwand 0xFFFFFF) != -1) - and (controlMode == 1 or party_member_obj(pid))) then begin - if not(critter == real_dude_obj) then begin - set_dude_obj(critter); + if (status == 1 and (AllowControl(pid) or controlMode == 1)) then begin + set_dude_obj(critter); + if (critter != real_dude_obj) then begin + if not(npcControl) then obj_set_light_level(real_dude_obj, 0, 0); // dude off light + npcControl := critter; + lightDist := get_object_data(critter, OBJ_DATA_LIGHT_DISTANCE); + lightInt := get_object_data(critter, OBJ_DATA_LIGHT_INTENSITY); + obj_set_light_level(critter, 100, 4); + if hasGeckoSkinning then critter_add_trait(critter, TRAIT_PERK, PERK_gecko_skinning_perk, 1); inControl := true; + end else if npcControl then begin + obj_set_light_level(dude_obj, 100, 4); // set default light + npcControl := 0; end if inControl then begin // center the screen on the controlled critter and remove roof tiles @@ -52,14 +71,26 @@ procedure combatturn_handler begin end end end else if inControl then begin - set_dude_obj(real_dude_obj); if displayName then hide_iface_tag(displayName); + if (status == -1 or AllowControl(pid) == false) then begin + set_dude_obj(real_dude_obj); + obj_set_light_level(dude_obj, 100, 4); + npcControl := 0; + end end end procedure gamemodechange_handler begin - if has_trait(TRAIT_PERK, real_dude_obj, PERK_gecko_skinning_perk) then hasGeckoSkinning := true; - if not(get_game_mode BWAND COMBAT) then inControl := false; + if not(hasGeckoSkinning) then hasGeckoSkinning := has_trait(TRAIT_PERK, real_dude_obj, PERK_gecko_skinning_perk); + if (inControl and not(get_game_mode BWAND COMBAT)) then begin + inControl := false; + if (dude_obj != real_dude_obj) then begin + set_dude_obj(real_dude_obj); + obj_set_light_level(dude_obj, 100, 4); + npcControl := 0; + end + move_to(dude_obj, dude_tile, dude_elevation); + end end procedure inventorymove_handler begin @@ -73,18 +104,33 @@ end procedure start begin if (game_loaded and sfall_ver_major >= 4) then begin + variable configSection := "CombatControl"; + set_perk_ranks(PERK_gecko_skinning_perk, 1); set_perk_level(PERK_gecko_skinning_perk, 999); // prevent it from appearing in the perk selection window + controlMode := GetConfig("CombatControl", "Mode", 0); if (controlMode > 2) then controlMode := 0; if (controlMode > 0) then begin + displayName := GetConfig("CombatControl", "DisplayName", 0); + + if displayName then begin + variable nameTag := add_iface_tag; + debug_msg("NPC control - added new box: " + nameTag); + if (nameTag <= 0) then begin // box added? + variable maxBoxCount := get_ini_setting("ddraw.ini|Misc|BoxBarCount"); + if (maxBoxCount < 0) then maxBoxCount := 9; + else maxBoxCount += 4; + if (displayName < 5 or displayName > maxBoxCount) then displayName := 0; + end else begin + displayName := nameTag; + end + displayNameColor := GetConfig("CombatControl", "DisplayNameColor", 0); + end + pidList := GetConfigListInt("CombatControl", "PIDList"); fix_array(pidList); - maxBoxCount := get_ini_setting("ddraw.ini|Misc|BoxBarCount") + 4; - displayName := GetConfig("CombatControl", "DisplayName", 0); - displayNameColor := GetConfig("CombatControl", "DisplayNameColor", 0); - if (maxBoxCount < 9) then maxBoxCount := 9; - if (displayName < 5 or displayName > maxBoxCount) then displayName := 0; + noCheckArray := (len_array(pidList) == 0); register_hook_proc(HOOK_COMBATTURN, combatturn_handler); register_hook_proc(HOOK_GAMEMODECHANGE, gamemodechange_handler); diff --git a/artifacts/mods/main.h b/artifacts/mods/main.h index 8b769a4c..84a4c50a 100644 --- a/artifacts/mods/main.h +++ b/artifacts/mods/main.h @@ -10,21 +10,21 @@ variable ini := "sfall-mods.ini"; variable translationIni; -// Gets the integer value from ddraw.ini +// Gets the integer value from ini procedure GetConfig(variable section, variable key, variable def) begin variable val := get_ini_setting(ini + "|" + section + "|" + key); if val == -1 then val := def; return val; end -// Gets the string value from ddraw.ini +// Gets the string value from ini procedure GetConfigStr(variable section, variable key, variable def) begin variable val := get_ini_string(ini + "|" + section + "|" + key); if val == -1 or val == "" then val := def; return val; end -// Gets the value from ddraw.ini as a temp array of strings +// Gets the value from ini as a temp array of strings procedure GetConfigList(variable section, variable key) begin variable val := get_ini_string(ini + "|" + section + "|" + key); if val == -1 or val == "" then return []; @@ -32,7 +32,7 @@ procedure GetConfigList(variable section, variable key) begin return string_split(val, ","); end -// Gets the value from ddraw.ini as a temp array of ints +// Gets the value from ini as a temp array of ints procedure GetConfigListInt(variable section, variable key) begin variable arr, i, item; diff --git a/artifacts/scripting/headers/define_extra.h b/artifacts/scripting/headers/define_extra.h index c13c8c70..89a28886 100644 --- a/artifacts/scripting/headers/define_extra.h +++ b/artifacts/scripting/headers/define_extra.h @@ -103,6 +103,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 // offsets for get_proto_data #define PROTO_PID (1) @@ -348,7 +349,7 @@ #define OBJ_DATA_FID (0x20) #define OBJ_DATA_ELEVATION (0x28) #define OBJ_DATA_PID (0x64) -#define OBJ_DATA_CID (0x68) +#define OBJ_DATA_CID (0x68) // combat id, used for savegame #define OBJ_DATA_SID (0x78) // items #define OBJ_DATA_CUR_CHARGES (0x3C) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index 693a3eb3..06137961 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -237,7 +237,8 @@ #define party_member_list_critters party_member_list(0) #define party_member_list_all party_member_list(1) - +// sfall_funcX macros +#define add_iface_tag sfall_func0("add_iface_tag") #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") @@ -269,12 +270,14 @@ #define item_weight(obj) sfall_func1("item_weight", obj) #define lock_is_jammed(obj) sfall_func1("lock_is_jammed", obj) #define loot_obj sfall_func0("loot_obj") +#define npc_engine_level_up(toggle) sfall_func1("npc_engine_level_up", toggle) #define obj_under_cursor(crSwitch, inclDude) sfall_func2("obj_under_cursor", crSwitch, inclDude) #define outlined_object sfall_func0("outlined_object") #define real_dude_obj sfall_func0("real_dude_obj") #define set_can_rest_on_map(map, elev, value) sfall_func3("set_can_rest_on_map", map, elev, value) #define set_car_intface_art(artIndex) sfall_func1("set_car_intface_art", artIndex) #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_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) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index e55d9c96..4c634c7d 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -242,12 +242,13 @@ int ret1 - overrides hard-coded handler and selects what should happen with HOOK_REMOVEINVENOBJ (hs_removeinvenobj.int) -Runs when an object is removed from a critters inventory for any reason +Runs when an object is removed from a container or critter's inventory for any reason -critter arg1 - the critter the object is being removed from +Obj arg1 - the owner that the object is being removed from item arg2 - the item that is being removed -int arg3 - a flag, or possibly the number of items to remove -int arg4 - The reason the object is being removed. (Actually, the site from which _item_remove_mult was called) +int arg3 - the number of items to remove +int arg4 - The reason the object is being removed (see RMOBJ_* constants) +Obj arg5 - The destination object when the item is moved to another object, otherwise 0 ------------------------------------------- diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 0d3fc9d7..ec3a7387 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -44,9 +44,9 @@ The get/set_sfall_global functions require an 8 characters long case sensitive s set_pickpocket_max and set_hit_chance_max effect all critters rather than just the player. set_skill_max can't be used to increase the skill cap above 300. set_perk_level_mod sets a modifier between +25 and -25 that is added/subtracted from the players level for the purposes of deciding which perks can be chosen. -set_fake_trait and set_fake_perk can be used to add additional traits and perks to the character screen. They will be saved correctly when the player saves and reloads games, but by themselves they will have no further effect on the character. For perks, the allowed range for levels is between 0 and 100; setting the level to 0 removes that perk. For traits, the level must be 0 or 1. The image is a numeric id that corresponds to an entry in skilldex.lst. The name is limited to 64 characters and the description to 1024 characters by sfall, but internal Fallout limits may be lower. +set_fake_trait and set_fake_perk can be used to add additional traits and perks to the character screen. They will be saved correctly when the player saves and reloads games, but by themselves they will have no further effect on the character. For perks, the allowed range for levels is between 0 and 100; setting the level to 0 removes that perk. For traits, the level must be 0 or 1. The image is a numeric id that corresponds to an entry in skilldex.lst. The name is limited to 63 characters and the description to 255 characters by sfall, but internal Fallout limits may be lower. -has_fake_trait and has_fake_perk return the number of levels the player has of the perks/traits with the given name. +has_fake_trait and has_fake_perk return the number of levels the player has of the perks/traits with the given name or ID of extra perk. perk_add_mode, set_selectable_perk, set_perkbox_title, hide_real_perks, show_real_perks and clear_selectable_perks control the behaviour of the select a perk box. set_selectable_perk can be used to add additional items by setting the 'active' parameter to 1, and to remove them again by setting it to 0. set_perkbox_title can be used to change the title of the box, or by using "" it will be set back to the default. hide and show_real_perks can be used to prevent the dialog from displaying any of the original 119 perks. perk_add_mode modifies what happens when a fake perk is selected from the perks dialog. It is treated as a set of flags - if bit 1 is set then it is added to the players traits, if bit 2 is set it is added to the players perks, and if bit 3 is set it is removed from the list of selectable perks. The default is 0x2. clear_selectable_perks restores the dialog to its default state. @@ -100,11 +100,12 @@ array - array ID to be used with array-related functions (actually an integer) > int game_loaded() - returns 1 the first time it is called after a new game or game load, and 0 any time after. It works on an individual basis for each script, so one script wont interfere with others. Its primary use is for global scripts, so that they know when to call set_global_script_repeat, but it can be called from normal scripts too. -> void inc_npc_level(string npc) -- takes an NPC name as an argument. The NPC must be in your party. This function ignores player level requirements and the minimum 3 player level delay between NPC level gains. It also ignores the random element, regardless of sfall's NPCAutoLevel setting. +> void inc_npc_level(int pid/string name) +- takes a party member PID or an NPC name (deprecated, for compatibility with sfall 4.1.5/3.8.15 or earlier) as an argument. The NPC must be in your party. +- This function ignores player level requirements and the minimum 3 player level delay between NPC level gains. It also ignores the random element, regardless of sfall's NPCAutoLevel setting. -> int get_npc_level(string npc) -- also takes the NPC name as an argument, and returns the NPC's current level. Again, the NPC needs to be in your party. +> int get_npc_level(int pid/string name) +- also takes the party member PID or NPC name (deprecated, for compatibility with sfall 4.1.5/3.8.15 or earlier) as an argument, and returns the NPC's current level. Again, the NPC needs to be in your party. > void set_car_current_town(int town) - changes the current town index for the player's car @@ -498,9 +499,13 @@ Some utility/math functions are available: > void sfall_func3("set_iface_tag_text", int tag, string text, int color) - sets the text messages and colors for custom notification boxes to the interface without the need to add messages to intrface.msg and set up the font colors in ddraw.ini -- tag value is the same as used in show_iface_tag, hide_iface_tag, and is_iface_tag_active. The valid range is from 5 to (4 + the value of BoxBarCount in ddraw.ini) +- tag value is the same as used in show_iface_tag, hide_iface_tag, and is_iface_tag_active. The valid range is from 5 to (4 + the value of BoxBarCount in ddraw.ini) or the number of the last custom box added using the add_ifaca_tag function - The text is limited to 19 characters -- available colors: 0 - green, 1 - red, 2 - white, 3 - yellow, 4 - dark yellow, 5 - blue, 6 - purple +- available colors: 0 - green, 1 - red, 2 - white, 3 - yellow, 4 - dark yellow, 5 - blue, 6 - purple, 7 - dull pink + +> int sfall_func0("add_iface_tag") +- adds one custom box to the current boxes, and returns the number of the added tag (-1 if the tags limit is exceeded) +- The maximum number of boxes is limited to 126 tags > void sfall_func1("inventory_redraw", int invSide) - redraws inventory list in the inventory/use inventory item on/loot/barter screens @@ -564,6 +569,15 @@ Some utility/math functions are available: > void sfall_func0("art_cache_clear") - clears the cache of FRM image files loaded into memory +> void sfall_func1("npc_engine_level_up", bool toggle) +- enables/disables the engine function that increases the level of party members in the player leveling process +- if the engine function is disabled, the process of leveling up party members should be performed by script functions + +> void sfall_func3("set_drugs_data", int type, int pid, int value) +- overrides the parameters of drugs set in the configuration file (DrugsFile setting in ddraw.ini) +- type: 0 - changes the value of NumEffects for the drug (see Drugs.ini for the description of NumEffects) + 1 - changes the duration of the addiction effect for the drug (a value of 1 = one game minute) + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/artifacts/scripting/sfall opcode list.txt b/artifacts/scripting/sfall opcode list.txt index 5ffd4839..017af837 100644 --- a/artifacts/scripting/sfall opcode list.txt +++ b/artifacts/scripting/sfall opcode list.txt @@ -162,8 +162,8 @@ 0x81a3 - int eax_available() 0x81a4 - void set_eax_environment(int environment) -0x81a5 - void inc_npc_level(string npc) -0x8241 - int get_npc_level(string npc) +0x81a5 - void inc_npc_level(int pid/string name) +0x8241 - int get_npc_level(int pid/string name) 0x81a6 - int get_viewport_x() 0x81a7 - int get_viewport_y() @@ -185,7 +185,7 @@ 0x81be - void set_perkbox_title(string title) 0x81bf - void hide_real_perks() 0x81c0 - void show_real_perks() -0x81c1 - int has_fake_perk(string name) +0x81c1 - int has_fake_perk(string name/int extraPerkID) 0x81c2 - int has_fake_trait(string name) 0x81c3 - void perk_add_mode(int type) 0x81c4 - void clear_selectable_perks() diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index 1e32d1d3..afa5df1e 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -19,6 +19,7 @@ #include #include "Functions.h" +#include "FunctionOffsets.h" #include "Structs.h" #include "Variables.h" #include "VariableOffsets.h" @@ -107,20 +108,41 @@ bool HeroIsFemale() { return (fo::func::stat_level(fo::var::obj_dude, fo::Stat::STAT_gender) == fo::Gender::GENDER_FEMALE); } +long CheckAddictByPid(fo::GameObject* critter, long pid) { + __asm { + mov eax, pid; + mov esi, critter; + call fo::funcoffs::item_d_check_addict_; + } + /* keyword 'return' is not needed, the compiler will do everything correctly */ +} + +void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set) { + Proto* protoPtr; + if (fo::func::proto_ptr(npc->protoId, &protoPtr) != -1) { + long bit = (1 << flag); + if (set) { + protoPtr->critter.critterFlags |= bit; + } else { + protoPtr->critter.critterFlags &= ~bit; + } + } +} + //--------------------------------------------------------- //print text to surface void PrintText(char *DisplayText, BYTE ColourIndex, DWORD Xpos, DWORD Ypos, DWORD TxtWidth, DWORD ToWidth, BYTE *ToSurface) { DWORD posOffset = Ypos * ToWidth + Xpos; __asm { - xor eax, eax - mov al, ColourIndex - push eax - mov edx, DisplayText - mov ebx, TxtWidth - mov ecx, ToWidth - mov eax, ToSurface - add eax, posOffset - call dword ptr ds:[FO_VAR_text_to_buf] + xor eax, eax; + mov al, ColourIndex; + push eax; + mov edx, DisplayText; + mov ebx, TxtWidth; + mov ecx, ToWidth; + mov eax, ToSurface; + add eax, posOffset; + call dword ptr ds:[FO_VAR_text_to_buf]; } } @@ -129,8 +151,8 @@ void PrintText(char *DisplayText, BYTE ColourIndex, DWORD Xpos, DWORD Ypos, DWOR DWORD GetTextHeight() { DWORD TxtHeight; __asm { - call dword ptr ds:[FO_VAR_text_height] //get text height - mov TxtHeight, eax + call dword ptr ds:[FO_VAR_text_height]; //get text height + mov TxtHeight, eax; } return TxtHeight; } @@ -140,9 +162,9 @@ DWORD GetTextHeight() { DWORD GetTextWidth(char *TextMsg) { DWORD TxtWidth; __asm { - mov eax, TextMsg - call dword ptr ds:[FO_VAR_text_width] //get text width - mov TxtWidth, eax + mov eax, TextMsg; + call dword ptr ds:[FO_VAR_text_width]; //get text width + mov TxtWidth, eax; } return TxtWidth; } @@ -152,9 +174,9 @@ DWORD GetTextWidth(char *TextMsg) { DWORD GetCharWidth(char CharVal) { DWORD charWidth; __asm { - mov al, CharVal - call dword ptr ds:[FO_VAR_text_char_width] - mov charWidth, eax + mov al, CharVal; + call dword ptr ds:[FO_VAR_text_char_width]; + mov charWidth, eax; } return charWidth; } @@ -164,9 +186,9 @@ DWORD GetCharWidth(char CharVal) { DWORD GetMaxTextWidth(char *TextMsg) { DWORD msgWidth; __asm { - mov eax, TextMsg - call dword ptr ds:[FO_VAR_text_mono_width] - mov msgWidth, eax + mov eax, TextMsg; + call dword ptr ds:[FO_VAR_text_mono_width]; + mov msgWidth, eax; } return msgWidth; } @@ -176,8 +198,8 @@ DWORD GetMaxTextWidth(char *TextMsg) { DWORD GetCharGapWidth() { DWORD gapWidth; __asm { - call dword ptr ds:[FO_VAR_text_spacing] - mov gapWidth, eax + call dword ptr ds:[FO_VAR_text_spacing]; + mov gapWidth, eax; } return gapWidth; } @@ -187,8 +209,8 @@ DWORD GetCharGapWidth() { DWORD GetMaxCharWidth() { DWORD charWidth = 0; __asm { - call dword ptr ds:[FO_VAR_text_max] - mov charWidth, eax + call dword ptr ds:[FO_VAR_text_max]; + mov charWidth, eax; } return charWidth; } diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index d07f6285..985b6aaa 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -60,6 +60,10 @@ GameObject* GetActiveItem(); bool HeroIsFemale(); +long CheckAddictByPid(fo::GameObject* critter, long pid); + +void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set); + // Print text to surface void PrintText(char *displayText, BYTE colorIndex, DWORD x, DWORD y, DWORD textWidth, DWORD destWidth, BYTE *surface); // gets the height of the currently selected font diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index 8278cb2e..dc5c72b2 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -231,6 +231,7 @@ enum ProtoId : long PID_STIMPAK = 40, PID_BOTTLE_CAPS = 41, PID_FIRST_AID_KIT = 47, + PID_RADAWAY = 48, PID_ANTIDOTE = 49, PID_DYNAMITE = 51, PID_GEIGER_COUNTER = 52, @@ -253,6 +254,8 @@ enum ProtoId : long PID_NUKA_COLA = 106, PID_RAD_X = 109, PID_PSYCHO = 110, + PID_BEER = 124, + PID_BOOZE = 125, PID_SUPER_STIMPAK = 144, PID_ACTIVE_FLARE = 205, PID_ACTIVE_DYNAMITE = 206, @@ -264,9 +267,11 @@ enum ProtoId : long PID_CHEMISTRY_MANUAL = 237, PID_JET = 259, PID_JET_ANTIDOTE = 260, + PID_DECK_OF_TRAGIC_CARDS = 306, PID_GECK = 366, PID_CAR_TRUNK = 455, PID_JESSE_CONTAINER = 467, + PID_Player = 16777216, PID_DRIVABLE_CAR = 33555441 }; @@ -620,6 +625,22 @@ enum AttackType : long ATKTYPE_PIERCINGKICK = 19 }; +enum AttackSubType : long +{ + NONE = 0, + UNARMED = 1, + MELEE = 2, + THROWING = 3, + GUNS = 4 +}; + +enum BodyType : long +{ + Biped = 0, + Quadruped = 1, + Robotic = 2 +}; + #define OBJFLAG_CAN_WEAR_ITEMS (0xf000000) #define OBJFLAG_HELD_IN_RIGHT (0x10000) diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index 316e654b..d891082b 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -160,6 +160,10 @@ long __stdcall db_get_file_list(const char* searchMask, char* * *fileList) { WRAP_WATCOM_CALL2(db_get_file_list_, searchMask, fileList) } +long __stdcall db_init(const char* path_dat, const char* path_patches) { + WRAP_WATCOM_CALL3(db_init_, path_dat, 0, path_patches) +} + // Check fallout paths for file long __stdcall CheckFile(char *fileName, DWORD *sizeOut) { WRAP_WATCOM_CALL2(db_dir_entry_, fileName, sizeOut) diff --git a/sfall/FalloutEngine/Functions.h b/sfall/FalloutEngine/Functions.h index 91f0e9be..af899736 100644 --- a/sfall/FalloutEngine/Functions.h +++ b/sfall/FalloutEngine/Functions.h @@ -76,6 +76,8 @@ long __stdcall db_fwriteByteCount(DbFile* file, const BYTE* cptr, long count); // returns number of elements in *fileList long __stdcall db_get_file_list(const char* searchMask, char* * *fileList); +long __stdcall db_init(const char* path_dat, const char* path_patches); + // prints message to debug.log file void __declspec() debug_printf(const char* fmt, ...); diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 9738254d..8053ea0f 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -48,6 +48,7 @@ WRAP_WATCOM_FUNC2(long, combat_turn, GameObject*, critter, long, isDudeTurn) WRAP_WATCOM_FUNC1(long, critter_is_dead, GameObject*, critter) WRAP_WATCOM_FUNC1(void, EndLoad, DbFile*, file) WRAP_WATCOM_FUNC1(long, folder_print_line, const char*, text) +WRAP_WATCOM_FUNC1(long, folder_print_seperator, const char*, text) WRAP_WATCOM_FUNC1(long, game_get_global_var, long, globalVar) WRAP_WATCOM_FUNC1(void, gdialogDisplayMsg, const char*, message) WRAP_WATCOM_FUNC1(long, gmouse_3d_set_mode, long, mode) @@ -86,9 +87,14 @@ WRAP_WATCOM_FUNC2(long, obj_pid_new, fo::GameObject*, object, long, pid) // checks/unjams jammed locks WRAP_WATCOM_FUNC1(long, obj_lock_is_jammed, GameObject*, object) WRAP_WATCOM_FUNC1(void, obj_unjam_lock, GameObject*, object) +WRAP_WATCOM_FUNC1(void, pc_flag_on, long, flag) +WRAP_WATCOM_FUNC2(void, perk_add_effect, GameObject*, critter, long, perkId) +WRAP_WATCOM_FUNC2(long, perk_can_add, GameObject*, critter, long, perkId) +//WRAP_WATCOM_FUNC2(void, perk_remove_effect, GameObject*, critter, long, perkId) WRAP_WATCOM_FUNC6(long, pick_death, GameObject*, attacker, GameObject*, target, GameObject*, weapon, long, amount, long, anim, long, hitFromBack) WRAP_WATCOM_FUNC0(void, proto_dude_update_gender) -WRAP_WATCOM_FUNC2(long, queue_find_first, GameObject*, object, long, qType) +WRAP_WATCOM_FUNC2(long*, queue_find_first, GameObject*, object, long, qType) +WRAP_WATCOM_FUNC2(long*, queue_find_next, GameObject*, object, long, qType) WRAP_WATCOM_FUNC3(long, register_object_animate, GameObject*, object, long, anim, long, delay) WRAP_WATCOM_FUNC3(long, register_object_animate_and_hide, GameObject*, object, long, anim, long, delay) // WRAP_WATCOM_FUNC3(long, register_object_animate_and_move_straight_, GameObject*, object; @@ -113,7 +119,12 @@ WRAP_WATCOM_FUNC1(long, register_object_must_erase, GameObject*, object) // WRAP_WATCOM_FUNC3(long, register_object_run_to_tile_, GameObject*, object; WRAP_WATCOM_FUNC3(long, register_object_take_out, GameObject*, object, long, holdFrameId, long, nothing) WRAP_WATCOM_FUNC3(long, register_object_turn_towards, GameObject*, object, long, tileNum, long, nothing) +WRAP_WATCOM_FUNC2(long, skill_dec_point_force, GameObject*, critter, long, skill) +WRAP_WATCOM_FUNC2(long, skill_inc_point_force, GameObject*, critter, long, skill) +WRAP_WATCOM_FUNC1(long, skill_is_tagged, long, skill) WRAP_WATCOM_FUNC2(long, stat_get_base_direct, GameObject*, critter, long, statID) +WRAP_WATCOM_FUNC2(long, stat_get_bonus, GameObject*, critter, long, statID) +WRAP_WATCOM_FUNC3(long, stat_set_bonus, GameObject*, critter, long, statID, long, amount) // adds experience points to PC WRAP_WATCOM_FUNC1(void, stat_pc_add_experience, long, amount) WRAP_WATCOM_FUNC1(long, text_font, long, fontNum) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 89bb67db..f86c0a4d 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -709,4 +709,27 @@ struct Queue { Queue* next; }; +struct QueueDrug { + DWORD pid; + fo::Stat stat0; + fo::Stat stat1; + fo::Stat stat2; + long amount0; + long amount1; + long amount2; +}; + +struct QueueAddict { + long init; // 1 = perk is not active yet + DWORD drugPid; + fo::Perk perkId; // effect of addiction +}; + +#pragma pack(1) +struct DrugInfoList { + DWORD itemPid; + long addictGvar; + long numEffects; +}; + } diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index d4e521d5..b3ae1499 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -48,6 +48,7 @@ #define FO_VAR_dialog_target_is_party 0x51884C #define FO_VAR_dropped_explosive 0x5190E0 #define FO_VAR_drugInfoList 0x5191CC +#define FO_VAR_editor_message_file 0x56FCA8 #define FO_VAR_edit_win 0x57060C #define FO_VAR_Educated 0x57082C #define FO_VAR_elevation 0x631D2C @@ -222,6 +223,7 @@ // colors #define FO_VAR_BlueColor 0x6A38EF #define FO_VAR_DarkGreenColor 0x6A3A90 +#define FO_VAR_DullPinkColor 0x6AB718 #define FO_VAR_GoodColor 0x6AB4EF #define FO_VAR_GreenColor 0x6A3CB0 #define FO_VAR_PeanutButter 0x6A82F3 diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 51147a36..8fe8a8b1 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -39,8 +39,9 @@ VAR_(dialogue_switch_mode, DWORD) VAR_(dialog_target, DWORD) VAR_(dialog_target_is_party, DWORD) VAR_(dropped_explosive, DWORD) -VAR_(drugInfoList, DWORD) +VARA(drugInfoList, DrugInfoList, 9) VAR_(edit_win, DWORD) +VAR_(editor_message_file, MessageList) VAR_(Educated, DWORD) VAR_(elevation, DWORD) VAR_(Experience_, DWORD) @@ -130,10 +131,10 @@ VAR_(optionsButtonUpKey, DWORD) VAR_(outlined_object, GameObject*) VAR_(partyMemberAIOptions, DWORD) VAR_(partyMemberCount, DWORD) -VAR_(partyMemberLevelUpInfoList, DWORD) +VAR_(partyMemberLevelUpInfoList, DWORD*) VAR_(partyMemberList, DWORD*) // each struct - 4 integers, first integer - objPtr VAR_(partyMemberMaxCount, DWORD) -VAR_(partyMemberPidList, DWORD) +VAR_(partyMemberPidList, DWORD*) VAR_(patches, char*) VAR_(paths, PathNode*) // array VAR2(pc_crit_succ_eff, CritInfo, 9, 6) // 9 body parts, 6 effects diff --git a/sfall/Modules/BarBoxes.cpp b/sfall/Modules/BarBoxes.cpp index 0251f2ce..43b13336 100644 --- a/sfall/Modules/BarBoxes.cpp +++ b/sfall/Modules/BarBoxes.cpp @@ -25,28 +25,26 @@ namespace sfall { -int BarBoxes::boxCount; // total count box -static int actualBoxCount; -static int sizeBox; +int BarBoxes::boxCount; // current box count +static int totalBoxCount, actualBoxCount; // total boxes and w/o vanilla +static int initCount = 5; // init config counter (5 - vanilla box) +static int sizeBox, setBoxIndex = 5; #define sSize (12) -struct sBox { +static struct sBox { DWORD msg; DWORD colour; void* mem; -}; +} *boxes; -#define tSize (25) -struct tBox { - bool hasText; - DWORD color; - char text[tSize - 5]; -}; - -static sBox* boxes; -static tBox* boxText; -static bool* boxesEnabled; -static DWORD* colorBakup; +#define tSize (24) +static struct tBox { + bool hasText; + char color; + char cfgColor; + bool isActive; + char text[tSize - 4]; +} *boxText; static bool setCustomBoxText; @@ -58,13 +56,14 @@ static const DWORD DisplayBoxesRet1 = 0x4615A8; static const DWORD DisplayBoxesRet2 = 0x4615BE; static void __declspec(naked) DisplayBoxesHack() { __asm { - mov edx, [boxesEnabled]; - mov ebx, 0; + mov edx, [boxText]; + xor ebx, ebx; start: - mov al, byte ptr [edx][ebx]; + imul eax, ebx, tSize; + mov al, byte ptr [edx][eax + 3]; // .isActive test al, al; jz next; - lea eax, [ebx + 5]; + lea eax, [ebx + 5]; // index box call fo::funcoffs::add_bar_box_; add esi, eax; next: @@ -83,27 +82,27 @@ fail: static void __declspec(naked) BarBoxesTextHack() { __asm { push ecx; // ecx = BoxIndex - sub ecx, 5; + sub ecx, 5; // subtract vanilla boxes imul ecx, tSize; mov esi, [boxText]; // boxText addr cmp byte ptr [esi][ecx], 1; // .hasText - jnz end; + je customText; + add esp, 4; + jmp fo::funcoffs::getmsg_; +customText: // get color - mov ebx, dword ptr [esi][ecx + 1]; // .color + movzx ebx, byte ptr [esi][ecx + 1]; // .color // set text - lea eax, [esi + ecx + 5]; // .text + lea eax, [esi + ecx + 4]; // .text // set color - pop ecx; + pop ecx; // restore BoxIndex imul ecx, sSize; mov esi, [boxes]; // boxes addr cmp dword ptr [esi][ecx + 4], 2; // .colour jb skip; - mov [esp + 0x440 - 0x20], ebx; // Color + mov [esp + 0x440 - 0x20], ebx; // set to Color skip: retn; -end: - add esp, 4; - jmp fo::funcoffs::getmsg_; } } @@ -111,7 +110,7 @@ static const DWORD SetIndexBoxRet = 0x4612E8; static void __declspec(naked) BarBoxesIndexHack() { __asm { mov eax, ds:[0x4612E2]; // fontnum - mov ecx, 5; // start index + mov ecx, setBoxIndex; // start index mov edx, ecx; jmp SetIndexBoxRet; } @@ -129,9 +128,10 @@ exitLoop: } } -static void ReconstructBarBoxes() { +static void ReconstructBarBoxes(int count) { + SafeWrite8(0x46140B, count); __asm { - call fo::funcoffs::refresh_box_bar_win_; + //call fo::funcoffs::refresh_box_bar_win_; call fo::funcoffs::reset_box_bar_win_; call fo::funcoffs::construct_box_bar_win_; } @@ -139,28 +139,28 @@ static void ReconstructBarBoxes() { static void ResetBoxes() { for (int i = 0; i < actualBoxCount; i++) { - boxesEnabled[i] = false; - boxes[i + 5].colour = colorBakup[i]; + boxText[i].isActive = false; + boxText[i].hasText = false; + boxes[i + 5].colour = boxText[i].cfgColor; } + if (setBoxIndex == 5) return; - if (!setCustomBoxText) return; - - //Restore boxes - SafeWrite32(0x461343, 0x00023D05); // call getmsg_ - ReconstructBarBoxes(); - SafeWrite8(0x461243, 0x31); - SafeWrite32(0x461244, 0x249489D2); - - setCustomBoxText = false; + // Restore boxes + setBoxIndex = 5; // set start + //SafeWrite32(0x461343, 0x00023D05); // call getmsg_ + ReconstructBarBoxes(totalBoxCount); + //SafeWrite8(0x461243, 0x31); + //SafeWrite32(0x461244, 0x249489D2); } void BarBoxes::SetText(int box, const char* text, DWORD color) { + setBoxIndex = box; boxes[box].colour = color; box -= 5; boxText[box].hasText = true; strncpy_s(boxText[box].text, text, _TRUNCATE); - DWORD clr; + char clr; switch (color) { case 2: clr = fo::var::WhiteColor; @@ -177,101 +177,140 @@ void BarBoxes::SetText(int box, const char* text, DWORD color) { case 6: clr = fo::var::GoodColor; break; + case 7: + clr = *(BYTE*)FO_VAR_DullPinkColor; + break; default: clr = fo::var::GreenColor; } boxText[box].color = clr; - bool* enabled = new bool[actualBoxCount]; - for (int i = 0; i < actualBoxCount; i++) { - enabled[i] = boxesEnabled[i]; - boxesEnabled[i] = false; - } - if (!setCustomBoxText) { + setCustomBoxText = true; MakeCall(0x461342, BarBoxesTextHack); // construct_box_bar_win_ MakeJump(0x461243, BarBoxesIndexHack); // construct_box_bar_win_ - setCustomBoxText = true; } - - ReconstructBarBoxes(); - - for (int i = 0; i < actualBoxCount; i++) { - boxesEnabled[i] = enabled[i]; - } - delete[] enabled; - - __asm call fo::funcoffs::refresh_box_bar_win_; + ReconstructBarBoxes(setBoxIndex + 1); } -void BarBoxes::init() { - - boxCount = 5 + GetConfigInt("Misc", "BoxBarCount", 5); - if (boxCount < 10) boxCount = 10; - if (boxCount > 100) boxCount = 100; - - actualBoxCount = boxCount - 5; - sizeBox = sSize * boxCount; - - boxes = new sBox[boxCount](); - boxText = new tBox[actualBoxCount](); - boxesEnabled = new bool[actualBoxCount](); - colorBakup = new DWORD[actualBoxCount](); - - memcpy(boxes, (void*)0x518FE8, sSize * 5); - - for (int i = 5; i < boxCount; i++) { - boxes[i].msg = 100 + i; - } - - auto boxBarColors = GetConfigString("Misc", "BoxBarColours", "", actualBoxCount + 1); - int size = boxBarColors.size(); - for (int i = 0; i < size; i++) { - if (boxBarColors[i] == '1') { - boxes[i + 5].colour = 1; - colorBakup[i] = 1; - } - } - +static void SetEngine(int count) { SafeWriteBatch((DWORD)boxes + 8, bboxMemAddr); //.mem SafeWrite32(0x4612FE, (DWORD)boxes + 4); //.colour SafeWrite32(0x46133C, (DWORD)boxes); //.msg - SafeWrite8(0x46127C, boxCount); - SafeWrite8(0x46140B, boxCount); + sizeBox = sizeof(sBox) * count; - if (boxCount > 10) { - MakeJump(0x461493, BarBoxesSizeHack); + static bool onlyOnce = false; + if (onlyOnce) return; + + if (count > 10) { + MakeJump(0x461493, BarBoxesSizeHack); // deconstruct_box_bar_win_ + onlyOnce = true; } else { SafeWrite8(0x461495, sizeBox); } +} + +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; + + actualBoxCount = initCount - 5; + + boxes = new sBox[initCount](); + boxText = new tBox[actualBoxCount](); + + memcpy(boxes, (void*)0x518FE8, sSize * 5); + for (int i = 5; i < initCount; i++) { + boxes[i].msg = 100 + i; + } + + auto boxBarColors = GetConfigString("Misc", "BoxBarColours", "", actualBoxCount + 1); + for (size_t i = 0; i < boxBarColors.size(); i++) { + if (boxBarColors[i] == '1') { + boxes[i + 5].colour = 1; // red color + boxText[i].cfgColor = 1; + } + } + + SetEngine(initCount); + SafeWrite8(0x46127C, initCount); // for only init + SafeWrite8(0x46140B, initCount); + MakeJump(0x4615A3, DisplayBoxesHack); - LoadGameHook::OnGameReset() += ResetBoxes; + totalBoxCount = boxCount = initCount; + + LoadGameHook::OnGameReset() += []() { + ResetBoxes(); + if (initCount != totalBoxCount) { + boxCount = initCount; + actualBoxCount = initCount - 5; + } + }; +} + +long BarBoxes::AddExtraBox() { + if (boxCount < totalBoxCount) { + actualBoxCount++; + boxCount++; + return MaxBox(); // just return the number of the previously added box + } + if (totalBoxCount > 126) return -1; // limit is exceeded + + void* data; + __asm { + mov eax, 2730; + call fo::funcoffs::mem_malloc_; + mov data, eax; + } + if (data == nullptr) return -1; // error on memory allocation + memcpy(data, boxes[1].mem, 2730); // copy image box to a new allocated memory + + sBox* boxes_new = new sBox[totalBoxCount + 1]; + memcpy(boxes_new, boxes, sizeof(sBox) * totalBoxCount); + delete[] boxes; + boxes = boxes_new; + + boxes[totalBoxCount].mem = data; + boxes[totalBoxCount].msg = 100; // set default text for added box + boxes[totalBoxCount].colour = 0; + boxCount = ++totalBoxCount; + + tBox* boxText_new = new tBox[actualBoxCount + 1]; + memcpy(boxText_new, boxText, sizeof(tBox) * actualBoxCount); + delete[] boxText; + boxText = boxText_new; + + memset((void*)&boxText[actualBoxCount], 0, sizeof(tBox)); + actualBoxCount++; + + SetEngine(totalBoxCount); + return MaxBox(); // current number of added box } bool BarBoxes::GetBox(int i) { if (i < 5 || i > BarBoxes::MaxBox()) return false; - return boxesEnabled[i - 5]; + return boxText[i - 5].isActive; } void BarBoxes::AddBox(int i) { if (i < 5 || i > BarBoxes::MaxBox()) return; - boxesEnabled[i - 5] = 1; - _asm call fo::funcoffs::refresh_box_bar_win_; + boxText[i - 5].isActive = true; + __asm call fo::funcoffs::refresh_box_bar_win_; } void BarBoxes::RemoveBox(int i) { if (i < 5 || i > BarBoxes::MaxBox()) return; - boxesEnabled[i - 5] = 0; - _asm call fo::funcoffs::refresh_box_bar_win_; + boxText[i - 5].isActive = false; + __asm call fo::funcoffs::refresh_box_bar_win_; } void BarBoxes::exit() { delete[] boxes; delete[] boxText; - delete[] boxesEnabled; - delete[] colorBakup; } } diff --git a/sfall/Modules/BarBoxes.h b/sfall/Modules/BarBoxes.h index b7a16cde..078f2f66 100644 --- a/sfall/Modules/BarBoxes.h +++ b/sfall/Modules/BarBoxes.h @@ -12,13 +12,13 @@ public: void init(); void exit() override; - static int MaxBox() { return boxCount - 1; } + static int MaxBox() { return boxCount - 1; } static void SetText(int box, const char* text, DWORD color); static bool GetBox(int); static void AddBox(int); static void RemoveBox(int); - + static long AddExtraBox(); }; } diff --git a/sfall/Modules/Books.cpp b/sfall/Modules/Books.cpp index 0fa0d493..bc2982c5 100644 --- a/sfall/Modules/Books.cpp +++ b/sfall/Modules/Books.cpp @@ -52,8 +52,10 @@ static void __declspec(naked) obj_use_book_hook() { mov ecx, eax; call FindBook; test eax, eax; - cmovnz edi, [eax + 4]; // msgID - cmovnz ecx, [eax + 8]; // skill + jz skip; + mov edi, [eax + 4]; // msgID + mov ecx, [eax + 8]; // skill +skip: jmp obj_use_book_hook_back; } } @@ -90,11 +92,11 @@ void Books::init() { bool includeVanilla = (GetPrivateProfileIntA("main", "overrideVanilla", 0, iniBooks) == 0); if (includeVanilla) BooksCount = 5; - int count = max(0, GetPrivateProfileIntA("main", "count", 0, iniBooks)); - if (count > BooksMax) count = BooksMax; + int count = GetPrivateProfileIntA("main", "count", 0, iniBooks); int n = 0; if (count > 0) { + if (count > BooksMax) count = BooksMax; books = new sBook[BooksCount + count]; if (includeVanilla) LoadVanillaBooks(); diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 8ee66c16..29e398f1 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -1,5 +1,6 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "Drugs.h" #include "LoadGameHook.h" #include "ScriptExtender.h" @@ -16,13 +17,51 @@ static DWORD weightOnBody = 0; static char textBuf[355]; +std::list drugsPid; + +static void __fastcall DrugPidPush(int pid) { + drugsPid.push_back(pid); +} + +static int __fastcall DrugPidPop() { + if (drugsPid.empty()) return 0; + int pid = drugsPid.front(); + drugsPid.pop_front(); + return pid; +} + +void BugFixes::DrugsSaveFix(HANDLE file) { + DWORD sizeWrite, count = drugsPid.size(); + WriteFile(file, &count, 4, &sizeWrite, 0); + if (!count) return; + for (auto it = drugsPid.begin(); it != drugsPid.end(); it++) { + int pid = *it; + WriteFile(file, &pid, 4, &sizeWrite, 0); + } + drugsPid.clear(); + return; +} + +bool BugFixes::DrugsLoadFix(HANDLE file) { + DWORD count, sizeRead; + ReadFile(file, &count, 4, &sizeRead, 0); + //if (sizeRead != 4) return true; + for (DWORD i = 0; i < count; i++) { + DWORD pid; + ReadFile(file, &pid, 4, &sizeRead, 0); + if (sizeRead != 4) return true; + drugsPid.push_back(pid); + } + return false; +} + void ResetBodyState() { __asm mov critterBody, 0; __asm mov sizeOnBody, 0; __asm mov weightOnBody, 0; } -void GameInitialization() { +static void MusicVolInitialization() { *(DWORD*)FO_VAR_gDialogMusicVol = *(DWORD*)FO_VAR_background_volume; // fix dialog music } @@ -37,9 +76,9 @@ static void __declspec(naked) NegateFixHack() { retn; isFloat: push ebx; - fld[esp]; + fld [esp]; fchs; - fstp[esp]; + fstp [esp]; pop ebx; call fo::funcoffs::pushLongStack_; mov edx, VAR_TYPE_FLOAT; @@ -192,97 +231,145 @@ skip: } } -static void __declspec(naked) item_d_check_addict_hack() { +static void __declspec(naked) item_d_check_addict_hack() { // replace engine function __asm { - mov edx, 2 // type = addiction - cmp eax, -1 // Has drug_pid? - je skip // No - xchg ebx, eax // ebx = drug_pid - mov eax, esi // eax = who - call fo::funcoffs::queue_find_first_ -loopQueue: - test eax, eax // Has something in the list? - jz end // No - cmp ebx, dword ptr [eax+0x4] // drug_pid == queue_addict.drug_pid? - je end // Has specific addiction - mov eax, esi // eax = who - mov edx, 2 // type = addiction - call fo::funcoffs::queue_find_next_ - jmp loopQueue + push 0x47A6A1; // return addr + mov edx, 2; // type = addiction + cmp eax, -1; // Has drug_pid? + jne skip; // No + mov eax, dword ptr ds:[FO_VAR_obj_dude]; + jmp fo::funcoffs::queue_find_first_; // return player addiction skip: - mov eax, dword ptr ds:[FO_VAR_obj_dude] - call fo::funcoffs::queue_find_first_ + mov ebx, eax; // ebx = drug_pid + mov eax, esi; // eax = who + call fo::funcoffs::queue_find_first_; +loopQueue: + test eax, eax; // Has something in the list? + jz end; // No + cmp ebx, dword ptr [eax + 0x4]; // drug_pid == queue_addict.drug_pid? + je end; // Has specific addiction + mov eax, esi; // eax = who + mov edx, 2; // type = addiction + call fo::funcoffs::queue_find_next_; + jmp loopQueue; end: - push 0x47A6A1 - retn + retn; } } -static void __declspec(naked) remove_jet_addict() { +static void __declspec(naked) RemoveJetAddictFunc() { __asm { - cmp eax, dword ptr ds:[FO_VAR_wd_obj] - jne end - cmp dword ptr [edx+0x4], PID_JET // queue_addict.drug_pid == PID_JET? - jne end - xor eax, eax - inc eax // Delete from queue - retn + cmp eax, dword ptr ds:[FO_VAR_wd_obj]; + jne end; + cmp dword ptr [edx + 0x4], PID_JET; // queue_addict.drug_pid == PID_JET? end: - xor eax, eax // Don't touch - retn + sete al; // 1 = Delete from queue, 0 = Don't touch + and eax, 0xFF; + retn; } } static void __declspec(naked) item_d_take_drug_hack() { __asm { - cmp dword ptr [eax], 0 // queue_addict.init - jne skip // Addiction is not active yet - mov edx, PERK_add_jet - mov eax, esi - call fo::funcoffs::perform_withdrawal_end_ + cmp dword ptr [eax], 0; // queue_addict.init + jne skip; // Addiction is not active yet + mov edx, PERK_add_jet; + mov eax, esi; + call fo::funcoffs::perform_withdrawal_end_; skip: - mov dword ptr ds:[FO_VAR_wd_obj], esi - mov eax, 2 // type = addiction - mov edx, offset remove_jet_addict - call fo::funcoffs::queue_clear_type_ - push 0x479FD1 - retn + mov dword ptr ds:[FO_VAR_wd_obj], esi; + mov eax, 2; // type = addiction + mov edx, offset RemoveJetAddictFunc; + call fo::funcoffs::queue_clear_type_; + push 0x479FD1; + retn; } } +static void __declspec(naked) item_d_load_subfix() { + __asm { + sub esp, 4; // proto buf +// mov [ebp], edi; // edi->queue_drug + xor ebp, ebp; // set drug_pid = 0 + mov ecx, 9; // vanilla count + mov esi, FO_VAR_drugInfoList; + mov ebx, 12; +loopDrug: + cmp dword ptr [esi + 8], 0; // drugInfoList.numeffects + je nextDrug; + mov edx, esp; + mov eax, [esi]; // drugInfoList.pid + call fo::funcoffs::proto_ptr_; + test eax, eax; + js nextDrug; // -1 - can't open proto + mov edx, [esp]; + mov eax, [edx + 0x24]; // drug.stat0 + cmp eax, [edi + 0x4]; // drug.stat0 == queue_drug.stat0? + jne nextDrug; // No + mov eax, [edx + 0x28]; // drug.stat1 + cmp eax, [edi + 0x8]; // drug.stat1 == queue_drug.stat1? + jne nextDrug; // No + mov eax, [edx + 0x2C]; // drug.stat2 + cmp eax, [edi + 0xC]; // drug.stat2 == queue_drug.stat2? + jne nextDrug; // No + mov eax, [edx + 0x30]; // drug.amount0 + cmp eax, [edi + 0x10]; // drug.amount0 == queue_drug.amount0? + jne nextDrug; // No + mov eax, [edx + 0x34]; // drug.amount1 + cmp eax, [edi + 0x14]; // drug.amount1 == queue_drug.amount1? + jne nextDrug; // No + mov eax, [edx + 0x38]; // drug.amount2 + cmp eax, [edi + 0x18]; // drug.amount2 == queue_drug.amount2? + je foundPid; // Yes +nextDrug: + lea esi, [esi + ebx]; + dec ecx; + jnz loopDrug; + cmp ebx, 12; + jnz end; // failed, this drug effect was not found + // try find in new drugs + call Drugs::GetDrugCount; + test eax, eax; + jz end; + mov ebx, SIZE_S_DRUGS; // sizeof structure + mov ecx, eax; + lea edx, drugs; + mov esi, [edx]; + jmp loopDrug; +foundPid: + mov ebp, [esi]; // drugInfoList.pid +end: + mov [edi], ebp; // queue_drug.drug_pid + xor eax, eax; + add esp, 4; + retn; + } +} + +// take the drug pid from the list after loading sfallgv.sav static void __declspec(naked) item_d_load_hack() { __asm { - sub esp, 4 - mov [ebp], edi // edi->queue_drug - mov ecx, 7 - mov esi, FO_VAR_drugInfoList + 12 -loopDrug: - cmp dword ptr [esi+8], 0 // drugInfoList.numeffects - je nextDrug - mov edx, esp - mov eax, [esi] // drugInfoList.pid - call fo::funcoffs::proto_ptr_ - mov edx, [esp] - mov eax, [edx+0x24] // drug.stat0 - cmp eax, [edi+0x4] // drug.stat0 == queue_drug.stat0? - jne nextDrug // No - mov eax, [edx+0x28] // drug.stat1 - cmp eax, [edi+0x8] // drug.stat1 == queue_drug.stat1? - jne nextDrug // No - mov eax, [edx+0x2C] // drug.stat2 - cmp eax, [edi+0xC] // drug.stat2 == queue_drug.stat2? - je foundPid // Yes -nextDrug: - add esi, 12 - loop loopDrug -foundPid: - jecxz end - mov eax, [esi] // drugInfoList.pid - mov [edi], eax // queue_drug.drug_pid -end: - xor eax, eax - add esp, 4 - retn + mov [ebp], edi; // edi->queue_drug + call DrugPidPop; + test eax, eax; + jnz skip; + jmp item_d_load_subfix; // if the pid was not saved, then try to find it +skip: + mov [edi], eax; // queue_drug.drug_pid + xor eax, eax; + retn; + } +} + +// add drug pid to the list to save to sfallgv.sav +static void __declspec(naked) item_d_save_hack() { + __asm { + pushadc; + mov ecx, [edx]; // drug pid + call DrugPidPush; + popadc; + mov ebx, 3; + retn; } } @@ -293,7 +380,7 @@ static void __declspec(naked) queue_clear_type_mem_free_hook() { } } -static void __declspec(naked) partyMemberCopyLevelInfo_stat_level_hook() { +static void __declspec(naked) partyMemberCopyLevelInfo_hook_stat_level() { __asm { nextArmor: mov eax, esi @@ -308,7 +395,7 @@ noArmor: } } -static void __declspec(naked) correctFidForRemovedItem_adjust_ac_hook() { +static void __declspec(naked) correctFidForRemovedItem_hook_adjust_ac() { __asm { call fo::funcoffs::adjust_ac_ nextArmor: @@ -323,40 +410,43 @@ end: } } -static void __declspec(naked) partyMemberCopyLevelInfo_hook() { +static void __declspec(naked) partyMemberIncLevels_hook() { __asm { - push eax - call fo::funcoffs::partyMemberCopyLevelInfo_ - pop ebx - cmp eax, -1 - je end - pushad - mov dword ptr ds:[FO_VAR_critterClearObj], ebx - mov edx, fo::funcoffs::critterClearObjDrugs_ - call fo::funcoffs::queue_clear_type_ - mov ecx, 8 - mov edi, FO_VAR_drugInfoList - mov esi, ebx + mov ebx, eax; // party member pointer + call fo::funcoffs::partyMemberCopyLevelInfo_; + cmp eax, -1; + je end; + xor edx, edx; // queue type (0) + mov eax, ebx; // source + call fo::funcoffs::queue_remove_this_; + push ecx; + push edi; + push esi; + mov ecx, 8; + mov edi, FO_VAR_drugInfoList; + mov esi, ebx; // pointer for fixed item_d_check_addict_ loopAddict: - mov eax, dword ptr [edi] // eax = drug pid - call fo::funcoffs::item_d_check_addict_ - test eax, eax // Has addiction? - jz noAddict // No - cmp dword ptr [eax], 0 // queue_addict.init - jne noAddict // Addiction is not active yet - mov edx, dword ptr [eax+0x8] // queue_addict.perk - mov eax, ebx - call fo::funcoffs::perk_add_effect_ + mov eax, dword ptr [edi]; // eax = drug pid + call fo::funcoffs::item_d_check_addict_; + test eax, eax; // Has addiction? + jz noAddict; // No + cmp dword ptr [eax], 0; // queue_addict.init + jne noAddict; // Addiction is not active yet + mov edx, dword ptr [eax + 0x8]; // queue_addict.perk + mov eax, ebx; + call fo::funcoffs::perk_add_effect_; noAddict: - add edi, 12 - loop loopAddict - popad + lea edi, [edi + 12]; + dec ecx; + jnz loopAddict; + pop esi; + pop edi; + pop ecx; end: - retn + retn; } } - static void __declspec(naked) gdProcessUpdate_hack() { __asm { add eax, esi @@ -649,7 +739,8 @@ found: add edx, [esp+0x40] // inventory_offset mov eax, ds:[FO_VAR_pud] mov ecx, [eax] // itemsCount - jecxz skip + test ecx, ecx + jz skip dec ecx cmp edx, ecx ja skip @@ -673,13 +764,15 @@ static void __declspec(naked) drop_ammo_into_weapon_hook() { jge skip // Yes lea edx, [eax + inventory] // Inventory mov ecx, [edx] // itemsCount - jcxz skip // inventory is empty (another excess check, but leave it) + test ecx, ecx + jz skip // inventory is empty (another excess check, but leave it) mov edx, [edx+8] // FirstItem nextItem: cmp ebp, [edx] // Our weapon? je foundItem // Yes add edx, 8 // Go to the next - loop nextItem + dec ecx + jnz nextItem jmp skip // Our weapon is not in inventory foundItem: cmp dword ptr [edx+4], 1 // Only one weapon? @@ -856,14 +949,16 @@ static void __declspec(naked) obj_load_func_hack() { jne skip test byte ptr [eax + damageFlags], DAM_KNOCKED_DOWN jz clear // No - pushad + pushadc + push ebx xor ecx, ecx - inc ecx - xor ebx, ebx - xor edx, edx - xchg edx, eax + mov edx, eax // object + mov ebx, ecx // extramem null + mov eax, ecx // time = 0 + inc ecx // type = 1 call fo::funcoffs::queue_add_ - popad + pop ebx + popadc clear: and word ptr [eax + damageFlags], ~(DAM_LOSE_TURN or DAM_KNOCKED_DOWN) // 0x7FFD skip: @@ -1807,6 +1902,81 @@ isLoad: } } +static void __declspec(naked) JesseContainerFid() { + __asm { + dec edx; // set fid to -1 + jmp fo::funcoffs::obj_new_; + } +} + +static void __declspec(naked) ai_search_inven_weap_hook() { + __asm { + call fo::funcoffs::item_w_subtype_; + cmp eax, THROWING; + jne fix; + retn; +fix: + xor eax, eax; + mov edx, [esi + ammoPid]; + test edx, edx; + js skip; + mov eax, GUNS; // set GUNS if has ammo pid +skip: + retn; + } +} + +static void __declspec(naked) map_age_dead_critters_hack() { + __asm { + test ecx, ecx; // dead_bodies_age + jz skip; // if (dead_bodies_age == No) exit func + cmp dword ptr [esp + 0x3C - 0x30 + 4], 0; +skip: + retn; + } +} + +static void __declspec(naked) partyFixMultipleMembers_hook() { + __asm { + mov edx, [esi + protoId]; + sar edx, 24; + cmp edx, OBJ_TYPE_CRITTER; + jne noDrop; // not critter + test [esi + damageFlags], DAM_DEAD; + jnz isDead; + cmp dword ptr [esi + health], 0; + jg noBlood; // is not dead +isDead: + // create generic blood + sub esp, 4; + mov eax, esp // object buf + mov edx, 0x5000004; // pid + call fo::funcoffs::obj_pid_new_; + add esp, 4; + cmp eax, -1; + je noBlood; + mov eax, [esp - 4]; // object +// mov [eax + frm], 3; // set frame + mov ebx, [esi + elevation]; + mov edx, [esi + tile]; + xor ecx, ecx; + call fo::funcoffs::obj_move_to_tile_; +noBlood: + mov eax, [esi + protoId]; + mov edx, 0x40; // noDrop flag + call fo::funcoffs::critter_flag_check_; + test eax, eax; + mov eax, esi; + jnz noDrop; // flag is set + mov edx, [esi + tile]; + call fo::funcoffs::item_drop_all_; + mov eax, esi; +noDrop: + xor edx, edx; + call fo::funcoffs::obj_erase_object_; + retn; + } +} void BugFixes::init() { @@ -1815,7 +1985,7 @@ void BugFixes::init() #endif // Missing game initialization - LoadGameHook::OnGameInit() += GameInitialization; + LoadGameHook::OnGameInit() += MusicVolInitialization; // fix vanilla negate operator on float values MakeCall(0x46AB68, NegateFixHack); @@ -1875,6 +2045,7 @@ void BugFixes::init() // Fix for gaining stats from more than two doses of a specific chem after save-load dlog("Applying fix for save-load unlimited drug use exploit.", DL_INIT); + MakeCall(0x47A25B, item_d_save_hack); MakeCall(0x47A243, item_d_load_hack); dlogr(" Done", DL_INIT); @@ -1888,14 +2059,14 @@ void BugFixes::init() // The same happens if you just order NPC to remove the armor through dialogue. //if (GetConfigInt("Misc", "ArmorCorruptsNPCStatsFix", 1)) { dlog("Applying fix for armor reducing NPC original stats when removed.", DL_INIT); - HookCall(0x495F3B, partyMemberCopyLevelInfo_stat_level_hook); - HookCall(0x45419B, correctFidForRemovedItem_adjust_ac_hook); + HookCall(0x495F3B, partyMemberCopyLevelInfo_hook_stat_level); + HookCall(0x45419B, correctFidForRemovedItem_hook_adjust_ac); dlogr(" Done", DL_INIT); //} // Fix of invalid stats when party member gains a level while being on drugs dlog("Applying fix for addicted party member level up bug.", DL_INIT); - HookCall(0x495D5C, partyMemberCopyLevelInfo_hook); + HookCall(0x495D5C, partyMemberIncLevels_hook); dlogr(" Done", DL_INIT); // Allow 9 options (lines of text) to be displayed correctly in a dialog window @@ -2159,7 +2330,7 @@ void BugFixes::init() MakeJump(0x424BA2, compute_damage_hack); dlogr(" Done", DL_INIT); - // Fix missing AC/DR mod stats when examining ammo in barter screen + // Fix missing AC/DR mod stats when examining ammo in the barter screen dlog("Applying fix for displaying ammo stats in barter screen.", DL_INIT); MakeCalls(obj_examine_func_hack_ammo0, {0x49B4AD, 0x49B504}); SafeWrite16(0x49B4B2, 0x9090); @@ -2167,7 +2338,7 @@ void BugFixes::init() MakeCall(0x49B563, obj_examine_func_hack_ammo1, 2); dlogr(" Done", DL_INIT); - // Display full item description for weapon/ammo in barter screen + // Display full item description for weapon/ammo in the barter screen showItemDescription = (GetConfigInt("Misc", "FullItemDescInBarter", 0) != 0); if (showItemDescription) { dlog("Applying full item description in barter patch.", DL_INIT); @@ -2212,7 +2383,7 @@ void BugFixes::init() SafeWrite8(0x4C1015, 0x90); HookCall(0x4C1042, wmSetupRandomEncounter_hook); - // Fix for unable to sell/give items in barter screen when the player/party member is overloaded + // Fix for unable to sell/give items in the barter screen when the player/party member is overloaded HookCalls(barter_attempt_transaction_hook_weight, {0x474C73, 0x474CCA}); // Fix for the underline position in the inventory display window when the item name is longer than one line @@ -2311,6 +2482,28 @@ void BugFixes::init() // Fix for the player's turn being skipped when loading a game saved in combat mode MakeCall(0x422E25, combat_hack_load); + + // Fix for the reserved item FRM being displayed in the top-left corner when in the loot/barter screens + HookCalls(JesseContainerFid, {0x473AC9, 0x475895}); + + // Fix the return value of has_skill function for incorrect skill numbers + SafeWrite32(0x4AA56B, 0); + + // Fix for NPC stuck in a loop of reloading melee/unarmed weapons when out of ammo + dlog("Applying fix for NPC stuck in a loop of reloading melee/unarmed weapons.", DL_INIT); + HookCall(0x429A2B, ai_search_inven_weap_hook); + dlogr(" Done", DL_INIT); + + // Fix for critters not being healed over time when entering the map if 'dead_bodies_age=No' is set in maps.txt + // also fix the zero initialization of a local variable to correct time for removing corpses and blood + dlog("Applying fix for the self-healing of critters when entering the map.", DL_INIT); + MakeCall(0x483356, map_age_dead_critters_hack); + SafeWrite32(0x4832A0, 0x9090C189); // mov ecx, eax (keep dead_bodies_age flag) + SafeWrite32(0x4832A4, 0x0C245489); // mov [esp + var_30], edx + dlogr(" Done", DL_INIT); + + // Fix for the removal of party member's corpse when loading the map + HookCall(0x4957B8, partyFixMultipleMembers_hook); } } diff --git a/sfall/Modules/BugFixes.h b/sfall/Modules/BugFixes.h index 0133f8bb..859a5a52 100644 --- a/sfall/Modules/BugFixes.h +++ b/sfall/Modules/BugFixes.h @@ -9,6 +9,9 @@ class BugFixes : public Module { public: const char* name() { return "BugFixes"; } void init(); + + static void DrugsSaveFix(HANDLE file); + static bool DrugsLoadFix(HANDLE file); }; extern int tagSkill4LevelBase; diff --git a/sfall/Modules/Criticals.cpp b/sfall/Modules/Criticals.cpp index 7d7846b4..630df8f4 100644 --- a/sfall/Modules/Criticals.cpp +++ b/sfall/Modules/Criticals.cpp @@ -247,7 +247,7 @@ static void CriticalTableOverride() { static void RemoveCriticalTimeLimitsPatch() { if (GetConfigInt("Misc", "RemoveCriticalTimelimits", 0)) { dlog("Removing critical time limits.", DL_INIT); - SafeWrite8(0x424118, 0xEB); // jmps 0x424131 + SafeWrite8(0x424118, 0xEB); // jump to 0x424131 SafeWrite8(0x4A3053, 0x0); SafeWrite8(0x4A3094, 0x0); dlogr(" Done", DL_INIT); diff --git a/sfall/Modules/Drugs.cpp b/sfall/Modules/Drugs.cpp new file mode 100644 index 00000000..8813a703 --- /dev/null +++ b/sfall/Modules/Drugs.cpp @@ -0,0 +1,335 @@ +/* + * 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 "..\main.h" +#include "..\FalloutEngine\Fallout2.h" +#include "LoadGameHook.h" + +#include "Drugs.h" + +namespace sfall +{ + +static const int drugsMax = 50; +static int drugsCount = 0; +static bool drugsReset = false; // true - need reset + +long Drugs::addictionGvarCount = 0; + +sDrugs *drugs = nullptr; + +static long __fastcall FindDrugVar(DWORD pid) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + //if (drugs[i].gvarID == 0) break; + return drugs[i].gvarID; + } + } + return -1; +} + +static void __declspec(naked) pid_to_gvar_hack() { + __asm { + push ecx; + push edx; + mov ecx, eax; + call FindDrugVar; + pop edx; + pop ecx; + test eax, eax; + jg end; // if gvar > 0 + mov ebx, ds:[FO_VAR_drugInfoList]; + retn; +end: // force exit func + add esp, 4; + pop edx; + pop ebx; + retn; + } +} + +static long __fastcall AllowUseDrug(fo::GameObject* critter, DWORD pid) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + if (drugs[i].numEffects == -1) break; // use NumEffects value from engine + if (drugs[i].numEffects == 0) return 1; + auto queue = (fo::QueueDrug*)fo::func::queue_find_first(critter, 0); + if (!queue) return 1; + int num = 0; + while (queue->pid != pid || ++num < drugs[i].numEffects) { + queue = (fo::QueueDrug*)fo::func::queue_find_next(critter, 0); + if (!queue) return 1; + } + return 0; // not allow + } + } + return -1; // not found +} + +static void __declspec(naked) drug_effect_allowed_hack() { + __asm { + push ecx; + call AllowUseDrug; // ecx - critter, edx - drug pid + pop ecx; + test eax, eax; + jge end; // if eax > -1 + mov edi, ds:[FO_VAR_drugInfoList]; + retn; +end: // force exit func + add esp, 4; + pop edi; + pop esi; + pop ecx; + pop ebx; + retn; + } +} + +static long _stdcall FindDrugTime(DWORD pid) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + if (drugs[i].addictTimeOff <= 0) break; + return drugs[i].addictTimeOff; + } + } + return 10080; // default time (7 days) +} + +static void __declspec(naked) perform_withdrawal_start_hack() { + __asm { + push ecx; + push edi; // drug pid + call FindDrugTime; + pop ecx; + mov ebx, eax; // time + retn; + } +} + +static long __fastcall PrintAddictionList(long isSeparator) { + long isSelect = 0; + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].gvarID > 0 && fo::var::game_global_vars[drugs[i].gvarID]) { + if (!isSeparator) { // print separator line + isSeparator = 1; + const char* message = fo::GetMessageStr(&fo::var::editor_message_file, 4001); + if (fo::func::folder_print_seperator(message)) { + fo::var::folder_card_title = (DWORD)message; + fo::var::folder_card_title2 = 0; + fo::var::folder_card_desc = (DWORD)fo::GetMessageStr(&fo::var::editor_message_file, 4101); + fo::var::folder_card_fid = 53; + isSelect = 1; + } + } + int msgNum = drugs[i].msgID; + const char* message = fo::GetMessageStr(&fo::var::editor_message_file, msgNum); + if (fo::func::folder_print_line(message)) { + fo::var::folder_card_title = (DWORD)message; + fo::var::folder_card_title2 = 0; + if (msgNum > 0) msgNum += 100; + fo::var::folder_card_desc = (DWORD)fo::GetMessageStr(&fo::var::editor_message_file, msgNum); + fo::var::folder_card_fid = drugs[i].frmID; + isSelect = 1; + } + } + } + return isSelect; +} + +static const DWORD list_karma_Ret = 0x43C1A3; +static void __declspec(naked) list_karma_hack() { + __asm { + mov ecx, [esp + 0x168 - 0x1C + 4]; + call PrintAddictionList; + or eax, edi; + jnz end; + mov edi, 47; + retn; +end: + add esp, 4; + jmp list_karma_Ret; + } +} + +static void CheckEngineNumEffects(int &set, long pid) { + switch (pid) { + case fo::PID_RADAWAY: + set = 4; + break; + case fo::PID_MENTATS: + set = 2; + break; + case fo::PID_BUFFOUT: + set = 1; + break; + case fo::PID_NUKA_COLA: + set = 0; + break; + case fo::PID_PSYCHO: + set = 3; + break; + case fo::PID_BEER: + set = 5; + break; + case fo::PID_BOOZE: + set = 6; + break; + case fo::PID_JET: + set = 7; + break; + case fo::PID_DECK_OF_TRAGIC_CARDS: + set = 8; + } +} + +static void CheckValidGvarNumber() { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].gvarID > 0) { + if (drugs[i].gvarID >= (long)fo::var::num_game_global_vars) { + drugs[i].gvarID = -1; + } + } + } +} + +static void ResetDrugs() { + if (!drugsReset) return; + drugsReset = false; + + int set = -1; + for (int i = 0; i < drugsCount; i++) { + CheckEngineNumEffects(set, drugs[i].drugPid); + if (set != -1) { + fo::var::drugInfoList[set].numEffects = drugs[i].iniNumEffects; + set = -1; + } + drugs[i].numEffects = drugs[i].iniNumEffects; + drugs[i].addictTimeOff = drugs[i].iniAddictTimeOff; + } +} + +long Drugs::GetDrugCount() { + return drugsCount; +} + +long Drugs::GetDrugPid(long n) { + return drugs[n].drugPid; +} + +long Drugs::GetDrugGvar(long n) { + return drugs[n].gvarID; +} + +long Drugs::SetDrugNumEffect(long pid, long effect) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + if (effect < 0) effect = 0; + int set = -1; + CheckEngineNumEffects(set, pid); + if (set != -1) fo::var::drugInfoList[set].numEffects = effect; + drugs[i].numEffects = effect; + drugsReset = true; + return 0; + } + } + return -1; +} + +long Drugs::SetDrugAddictTimeOff(long pid, long time) { + for (int i = 0; i < drugsCount; i++) { + if (drugs[i].drugPid == pid) { + if (time < 0) time = 0; + drugs[i].addictTimeOff = time; + drugsReset = true; + return 0; + } + } + return -1; +} + +void Drugs::init() { + auto drugsFile = GetConfigString("Misc", "DrugsFile", "", MAX_PATH); + if (!drugsFile.empty()) { + dlog("Applying drugs patch...", DL_INIT); + const char* iniDrugs = drugsFile.insert(0, ".\\").c_str(); + + if (GetPrivateProfileIntA("main", "JetWithdrawal", 0, iniDrugs) == 1) SafeWrite8(0x47A3A8, 0); + + int count = GetPrivateProfileIntA("main", "Count", 0, iniDrugs); + if (count > 0) { + if (count > drugsMax) count = drugsMax; + drugs = new sDrugs[count](); + + int set = -1; + char section[4]; + for (int i = 1; i <= count; i++) { + _itoa_s(i, section, 10); + int pid = GetPrivateProfileIntA(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); + if (set != -1) { + if (ef < -1) { + ef = -1; + } else if (ef > -1) { + drugs[drugsCount].iniNumEffects = ef; + fo::var::drugInfoList[set].numEffects = ef; // also set to engine + } else { + drugs[drugsCount].iniNumEffects = fo::var::drugInfoList[set].numEffects; // default value from engine + } + set = -1; + 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); + drugs[drugsCount].gvarID = max(0, gvar); // not allow negative values + if (gvar) { + int msg = GetPrivateProfileIntA(section, "TextID", -1, iniDrugs); + drugs[drugsCount].msgID = (msg > 0) ? msg : -1; + drugs[drugsCount].frmID = GetPrivateProfileIntA(section, "FrmID", -1, iniDrugs); + addictionGvarCount++; + } + } + drugsCount++; + } + } + if (drugsCount) { + MakeCall(0x479EEC, drug_effect_allowed_hack, 1); + MakeCall(0x43C15C, list_karma_hack, 2); + MakeCall(0x47A5B8, pid_to_gvar_hack, 1); + MakeCall(0x47A50C, perform_withdrawal_start_hack); + SafeWrite32(0x47A523, 0x9090EBD1); // shr ebx, 1 (fix for trait drug addict) + SafeWrite8(0x47A527, 0x90); + + if (addictionGvarCount) { + LoadGameHook::OnAfterGameInit() += CheckValidGvarNumber; + } + LoadGameHook::OnGameReset() += ResetDrugs; + } + } + dlog_f(" (%d/%d drugs) Done\n", DL_INIT, drugsCount, count); + } +} + +void Drugs::exit() { + if (drugs) delete[] drugs; +} + +} diff --git a/sfall/Modules/Drugs.h b/sfall/Modules/Drugs.h new file mode 100644 index 00000000..b82666b1 --- /dev/null +++ b/sfall/Modules/Drugs.h @@ -0,0 +1,57 @@ +/* + * 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 +{ + +class Drugs : public Module { +public: + const char* name() { return "Drugs"; } + void init(); + void exit() override; + + static long addictionGvarCount; + + static long GetDrugCount(); + static long GetDrugPid(long n); + static long GetDrugGvar(long n); + + static long SetDrugNumEffect(long pid, long effect); + static long SetDrugAddictTimeOff(long pid, long time); +}; + +#define SIZE_S_DRUGS (32) + +struct sDrugs { + DWORD drugPid; // don't move + long gvarID; // don't move + long numEffects; // don't move + long addictTimeOff; + long msgID; + long frmID; + long iniNumEffects; + long iniAddictTimeOff; +}; + +extern sDrugs *drugs; + +} diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index 49932755..3e219a0e 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -16,22 +16,28 @@ static void __declspec(naked) RemoveObjHook() { __asm { mov ecx, [esp + 8]; // call addr HookBegin; - mov args[0], eax; - mov args[4], edx; - mov args[8], ebx; + mov args[0], eax; // source + mov args[4], edx; // item + mov args[8], ebx; // count mov args[12], ecx; - pushad; + xor esi, esi; + xor ecx, 0x47761D; // from item_move_func_ + cmovz esi, ebp; // target + mov args[16], esi; + push edi; + push ebp; + push eax; + push edx; } - argCount = 4; + argCount = 5; RunHookScript(HOOK_REMOVEINVENOBJ); EndHook(); __asm { - popad; - push edi; - push ebp; - sub esp, 0x0C; + pop edx; + pop eax; + sub esp, 0x0C; jmp RemoveObjHookRet; } } @@ -43,14 +49,14 @@ static void __declspec(naked) MoveCostHook() { mov args[4], edx; call fo::funcoffs::critter_compute_ap_from_distance_; mov args[8], eax; - pushad; + pushadc; } argCount = 3; RunHookScript(HOOK_MOVECOST); __asm { - popad; + popadc; cmp cRet, 1; cmovge eax, rets[0]; HookEnd; diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index df31aa54..8a505ddb 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -51,6 +51,7 @@ namespace sfall popadc } static Delegate<> onGameInit; +static Delegate<> onAfterGameInit; static Delegate<> onGameExit; static Delegate<> onGameReset; static Delegate<> onBeforeGameStart; @@ -128,6 +129,7 @@ static void _stdcall SaveGame2() { WriteFile(h, &data, 4, &size, 0); Perks::save(h); script::SaveArrays(h); + BugFixes::DrugsSaveFix(h); CloseHandle(h); } else { goto errorSave; @@ -224,6 +226,7 @@ static bool LoadGame_Before() { ReadFile(h, &data, 4, &size, 0); Worldmap::SetAddedYears(data >> 16); if (size != 4 || !Perks::load(h) || script::LoadArrays(h)) goto errorLoad; + if (BugFixes::DrugsLoadFix(h)) goto errorLoad; CloseHandle(h); } else { dlogr("Cannot open sfallgv.sav - assuming non-sfall save.", DL_MAIN); @@ -321,10 +324,14 @@ static void __declspec(naked) main_load_new_hook() { } } -static void __stdcall GameInitialized() { +static void __stdcall GameInitialization() { onGameInit.invoke(); } +static void __stdcall GameInitialized() { + onAfterGameInit.invoke(); +} + static void __stdcall GameExit() { onGameExit.invoke(); } @@ -335,10 +342,14 @@ static void __stdcall GameClose() { static void __declspec(naked) main_init_system_hook() { __asm { + pushadc; + call GameInitialization; + popadc; + call fo::funcoffs::main_init_system_; pushadc; call GameInitialized; popadc; - jmp fo::funcoffs::main_init_system_; + retn; } } @@ -642,7 +653,7 @@ void LoadGameHook::init() { HookCalls(EscMenuHook2, {0x4433BE}); HookCalls(OptionsMenuHook, {0x48FCE4, 0x48FD17, 0x48FD4D, 0x48FD6A, 0x48FD87, 0x48FDB3}); HookCalls(HelpMenuHook, {0x443A50}); - HookCalls(CharacterHook, {0x443320, 0x4A73EB, 0x4A740A}); + HookCalls(CharacterHook, {0x443320}); // 0x4A73EB, 0x4A740A for character creation HookCalls(DialogHook, {0x445748}); HookCalls(PipboyHook, {0x443463, 0x443605}); HookCalls(SkilldexHook, {0x4434AC, 0x44C7BD}); @@ -662,6 +673,10 @@ Delegate<>& LoadGameHook::OnGameInit() { return onGameInit; } +Delegate<>& LoadGameHook::OnAfterGameInit() { + return onAfterGameInit; +} + Delegate<>& LoadGameHook::OnGameExit() { return onGameExit; } diff --git a/sfall/Modules/LoadGameHook.h b/sfall/Modules/LoadGameHook.h index 354a25fd..17dfacd6 100644 --- a/sfall/Modules/LoadGameHook.h +++ b/sfall/Modules/LoadGameHook.h @@ -29,9 +29,12 @@ public: const char* name() { return "LoadGameHook"; } void init(); - // Invoked when the game has initialized (game_init_ was called). + // Invoked before game initialization (game_init_ was called). static Delegate<>& OnGameInit(); + // Invoked when the game has been initialized + static Delegate<>& OnAfterGameInit(); + // Invoked when the game exits to main menu static Delegate<>& OnGameExit(); diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 309e5915..4f65a04c 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -37,6 +37,8 @@ static bool femaleCheck = false; // flag for check female dialog file static DWORD format; static bool cutsPatch = false; +static std::vector patchFiles; + static void CheckPlayerGender() { isFemale = fo::HeroIsFemale(); @@ -66,7 +68,7 @@ static void __declspec(naked) scr_get_dialog_msg_file_hack1() { push msgFemaleFolder; jmp scr_get_dialog_msg_file_Back; default: - push FO_VAR_aDialogS_msg; // default "dialog\%s.msg" + push FO_VAR_aDialogS_msg; // default "dialog\%s.msg" jmp scr_get_dialog_msg_file_Back; } } @@ -99,81 +101,102 @@ static void __declspec(naked) gnw_main_hack() { } } -static void __declspec(naked) removeDatabase() { - __asm { - cmp eax, -1 - je end - mov ebx, ds:[FO_VAR_paths] - mov ecx, ebx -nextPath: - mov edx, [esp+0x104+4+4] // path_patches - mov eax, [ebx] // database.path - call fo::funcoffs::stricmp_ - test eax, eax // found path? - jz skip // Yes - mov ecx, ebx - mov ebx, [ebx+0xC] // database.next - jmp nextPath -skip: - mov eax, [ebx+0xC] // database.next - mov [ecx+0xC], eax // database.next - xchg ebx, eax - cmp eax, ecx - jne end - mov ds:[FO_VAR_paths], ebx -end: - retn +static fo::PathNode* __fastcall RemoveDatabase(const char* pathPatches) { + auto paths = fo::var::paths; // curr.node (beginning of the load order) + auto _paths = paths; // prev.node + + while (paths) { + if (_stricmp(paths->path, pathPatches) == 0) { // found path + fo::PathNode* nextPaths = paths->next; // pointer to the node of the next path +// TODO: need to check if this condition is used correctly + if (paths != _paths) + _paths->next = nextPaths; // replace the pointer in the previous node, removing the current(found) path from the load order + else // if the current node is equal to the previous node + fo::var::paths = nextPaths; // set the next node at the beginning of the load order + return paths; // return the pointer of the current removed node (save the pointer) + } + _paths = paths; // prev.node <- curr.node + paths = paths->next; // take a pointer to the next path from the current node } + return nullptr; // it's possible that this will create an exceptional situation for the game, although such a situation should not arise } +// Remove master_patches from the load order static void __declspec(naked) game_init_databases_hack1() { __asm { - call removeDatabase - mov ds:[FO_VAR_master_db_handle], eax - retn + cmp eax, -1; + je skip; + mov ecx, [esp + 0x104 + 4]; // path_patches + call RemoveDatabase; +skip: + mov ds:[FO_VAR_master_db_handle], eax; // the pointer of master_patches node will be saved here + retn; } } +// Remove critter_patches from the load order static void __declspec(naked) game_init_databases_hack2() { __asm { - cmp eax, -1 - je end - mov eax, ds:[FO_VAR_master_db_handle] - mov eax, [eax] // eax = master_patches.path - call fo::funcoffs::xremovepath_ - dec eax // remove path (critter_patches == master_patches)? - jz end // Yes - inc eax - call removeDatabase + cmp eax, -1; + je end; + mov eax, ds:[FO_VAR_master_db_handle]; // pointer to master_patches node + mov eax, [eax]; // eax = master_patches.path + call fo::funcoffs::xremovepath_; + dec eax; // remove path (critter_patches == master_patches)? + jz end; // Yes (jump if 0) + mov ecx, [esp + 0x104 + 4]; // path_patches + call RemoveDatabase; end: - mov ds:[FO_VAR_critter_db_handle], eax - retn + mov ds:[FO_VAR_critter_db_handle], eax; // the pointer of critter_patches node will be saved here + retn; } } -static void __declspec(naked) game_init_databases_hook() { -// eax = _master_db_handle - __asm { - mov ecx, ds:[FO_VAR_critter_db_handle] - mov edx, ds:[FO_VAR_paths] - jecxz skip - mov [ecx+0xC], edx // critter_patches.next->_paths - mov edx, ecx -skip: - mov [eax+0xC], edx // master_patches.next - mov ds:[FO_VAR_paths], eax - retn +static void __stdcall InitExtraPatches() { + for (auto it = patchFiles.begin(); it != patchFiles.end(); it++) { + fo::func::db_init(it->c_str(), 0); + } +} + +static void __fastcall game_init_databases_hook() { + fo::PathNode* master_patches; + __asm mov master_patches, eax; // eax = _master_db_handle + + if (!patchFiles.empty()) InitExtraPatches(); + + fo::PathNode* critter_patches = (fo::PathNode*)fo::var::critter_db_handle; + fo::PathNode* paths = fo::var::paths; // beginning of the load order + // insert master_patches/critter_patches at the beginning of the load order + if (critter_patches) { + critter_patches->next = paths; // critter_patches.next -> paths + paths = critter_patches; + } + master_patches->next = paths; // master_patches.next -> paths + fo::var::paths = master_patches; // set master_patches node at the beginning of the load order +} + +static void GetExtraPatches() { + char patchFile[12] = "PatchFile"; + for (int i = 0; i < 100; i++) { + _itoa(i, &patchFile[9], 10); + auto patch = GetConfigString("ExtraPatches", patchFile, "", MAX_PATH); + if (patch.empty() || GetFileAttributes(patch.c_str()) == INVALID_FILE_ATTRIBUTES) continue; + patchFiles.push_back(patch); } } void LoadOrder::init() { + GetExtraPatches(); + if (GetConfigInt("Misc", "DataLoadOrderPatch", 0)) { dlog("Applying data load order patch.", DL_INIT); MakeCall(0x444259, game_init_databases_hack1); MakeCall(0x4442F1, game_init_databases_hack2); - HookCall(0x44436D, &game_init_databases_hook); - SafeWrite8(0x4DFAEC, 0x1D); // error correction + HookCall(0x44436D, game_init_databases_hook); + SafeWrite8(0x4DFAEC, 0x1D); // error correction (ecx > ebx) dlogr(" Done", DL_INIT); + } else if (!patchFiles.empty()) { + HookCall(0x44436D, InitExtraPatches); } femaleMsgs = GetConfigInt("Misc", "FemaleDialogMsgs", 0); diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index ca765b69..a6eef2a3 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -39,8 +39,6 @@ static char versionString[65]; static int* scriptDialog = nullptr; -bool npcAutoLevelEnabled; - static const DWORD PutAwayWeapon[] = { 0x411EA2, // action_climb_ladder_ 0x412046, // action_use_an_item_on_object_ @@ -284,25 +282,27 @@ really_end: static DWORD RetryCombatLastAP; static DWORD RetryCombatMinAP; static void __declspec(naked) RetryCombatHook() { + using namespace fo; + using namespace Fields; __asm { - mov RetryCombatLastAP, 0; + mov RetryCombatLastAP, 0; retry: - mov eax, esi; - xor edx, edx; call fo::funcoffs::combat_ai_; process: - cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; - jle next; + cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; + jle next; call fo::funcoffs::process_bk_; - jmp process; + jmp process; next: - mov eax, [esi+0x40]; - cmp eax, RetryCombatMinAP; - jl end; - cmp eax, RetryCombatLastAP; - je end; - mov RetryCombatLastAP, eax; - jmp retry; + mov eax, [esi + movePoints]; + cmp eax, RetryCombatMinAP; + jl end; + cmp eax, RetryCombatLastAP; + je end; + mov RetryCombatLastAP, eax; + mov eax, esi; + xor edx, edx; + jmp retry; end: retn; } @@ -351,7 +351,7 @@ fail: static void __declspec(naked) op_obj_can_see_obj_hook() { __asm { push fo::funcoffs::obj_shoot_blocking_at_; // check hex objects func pointer - push 0x20; // flags, 0x20 = check shootthru + push 0x20; // flags, 0x20 = check ShootThru mov ecx, dword ptr [esp + 0x0C]; // buf **ret_objStruct push ecx; xor ecx, ecx; @@ -465,15 +465,6 @@ static const DWORD EncounterTableSize[] = { 0x4C0815, 0x4C0D4A, 0x4C0FD4, }; -void NpcAutoLevelPatch() { - npcAutoLevelEnabled = GetConfigInt("Misc", "NPCAutoLevel", 0) != 0; - if (npcAutoLevelEnabled) { - dlog("Applying NPC autolevel patch.", DL_INIT); - SafeWrite8(0x495CFB, 0xEB); // jmps 0x495D28 (skip random check) - dlogr(" Done", DL_INIT); - } -} - void AdditionalWeaponAnimsPatch() { if (GetConfigInt("Misc", "AdditionalWeaponAnims", 0)) { dlog("Applying additional weapon animations patch.", DL_INIT); @@ -619,19 +610,19 @@ void DontTurnOffSneakIfYouRunPatch() { void CombatProcFix() { //Ray's combat_p_proc fix dlog("Applying combat_p_proc fix.", DL_INIT); - SafeWrite32(0x0425253, ((DWORD)&Combat_p_procFix) - 0x0425257); - SafeWrite8(0x0424dbc, 0xE9); - SafeWrite32(0x0424dbd, 0x00000034); + HookCall(0x425252, Combat_p_procFix); + SafeWrite8(0x424DBC, 0xE9); + SafeWrite32(0x424DBD, 0x00000034); dlogr(" Done", DL_INIT); } void MultiPatchesPatch() { - if (GetConfigInt("Misc", "MultiPatches", 0)) { + //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() { @@ -655,7 +646,7 @@ void ApplyNpcExtraApPatch() { RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0); if (RetryCombatMinAP > 0) { dlog("Applying retry combat patch.", DL_INIT); - HookCall(0x422B94, &RetryCombatHook); + HookCall(0x422B94, RetryCombatHook); // combat_turn_ dlogr(" Done", DL_INIT); } } @@ -919,10 +910,26 @@ void MiscPatches::init() { dlogr(" Done", DL_INIT); } + int time = GetConfigInt("Misc", "CorpseDeleteTime", 6); // time in days + if (time != 6) { + dlog("Applying corpse deletion time patch.", DL_INIT); + if (time <= 0) { + time = 12; // hours + } else if (time > 13) { + time = 13 * 24; + } else { + time *= 24; + } + SafeWrite32(0x483348, time); + dlogr(" Done", DL_INIT); + } + + int gvar = GetConfigInt("Misc", "SpecialDeathGVAR", fo::GVAR_MODOC_SHITTY_DEATH); + if (gvar != fo::GVAR_MODOC_SHITTY_DEATH) SafeWrite32(0x440C2A, gvar); + LoadGameHook::OnBeforeGameStart() += BodypartHitChances; // set on start & load CombatProcFix(); - NpcAutoLevelPatch(); DialogueFix(); AdditionalWeaponAnimsPatch(); MultiPatchesPatch(); diff --git a/sfall/Modules/MiscPatches.h b/sfall/Modules/MiscPatches.h index 274844e0..43298074 100644 --- a/sfall/Modules/MiscPatches.h +++ b/sfall/Modules/MiscPatches.h @@ -35,9 +35,5 @@ struct CodeData { BYTE db = 0x90; }; -extern bool npcAutoLevelEnabled; - -void _stdcall SetMapMulti(float d); - } diff --git a/sfall/Modules/Objects.cpp b/sfall/Modules/Objects.cpp index dbe9c9eb..83fb1f39 100644 --- a/sfall/Modules/Objects.cpp +++ b/sfall/Modules/Objects.cpp @@ -32,8 +32,10 @@ long Objects::uniqueID = UniqueID::Start; // saving to sfallgv.sav // Assigns a new unique identifier to an object if it has not been previously assigned // the identifier is saved with the object in the saved game and this can used in various script +// player ID = 18000, all party members have ID = 18000 + its pid (file number of prototype) long Objects::SetObjectUniqueID(fo::GameObject* obj) { - if (obj->id > UniqueID::Start || obj == fo::var::obj_dude) return obj->id; // dude id = 1800. TODO: perhaps his id needs to be changed to 0x10000000 + long id = obj->id; + if (id > UniqueID::Start || obj == fo::var::obj_dude || (id >= 18000 && id < 83536)) return id; // 65535 maximum possible number of prototypes if ((DWORD)uniqueID >= UniqueID::End) uniqueID = UniqueID::Start; obj->id = ++uniqueID; diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 17562e8c..2f08ee70 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -16,12 +16,12 @@ * along with this program. If not, see . */ -#include -#include +//#include #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "HookScripts\InventoryHs.h" +#include "Drugs.h" #include "HookScripts.h" #include "LoadGameHook.h" @@ -30,11 +30,12 @@ namespace sfall { +bool npcAutoLevelEnabled; +bool npcEngineLevelUp = true; + bool isControllingNPC = false; bool skipCounterAnim = false; -static DWORD controlMode; -static std::vector allowedCritterPids; static int delayedExperience; static bool switchHandHookInjected = false; @@ -54,12 +55,47 @@ static struct DudeState { DWORD itemCurrentItem; fo::ItemButtonItem itemButtonItems[2]; long perkLevelDataList[fo::PERK_count]; - //DWORD drug_gvar[6]; - //DWORD jet_gvar; + long addictGvar[8]; long tag_skill[4]; //DWORD bbox_sneak; + long* extendAddictGvar = nullptr; } realDude; +static void SaveAddictGvarState() { + int n = 0; + for (int i = 0; i < Drugs::GetDrugCount(); i++) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID > 0) realDude.extendAddictGvar[n++] = fo::var::game_global_vars[gvarID]; + } +} + +static void RestoreAddictGvarState() { + int n = 0; + for (int i = 0; i < Drugs::GetDrugCount(); i++) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID > 0) fo::var::game_global_vars[gvarID] = realDude.extendAddictGvar[n++]; + } +} + +static bool SetAddictGvar(fo::GameObject* npc) { + bool isAddict = false; + int count = Drugs::GetDrugCount(); + for (int i = 0; i < count; i++) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID > 0) fo::var::game_global_vars[gvarID] = 0; + } + for (int i = 0; i < count; i++) { + long pid = Drugs::GetDrugPid(i); + if (pid > 0) { + long gvarID = Drugs::GetDrugGvar(i); + if (gvarID <= 0 || !fo::CheckAddictByPid(npc, pid)) continue; + fo::var::game_global_vars[gvarID] = 1; + isAddict = true; + } + } + return isAddict; +} + // saves the state of PC before moving control to NPC static void SaveRealDudeState() { realDude.obj_dude = fo::var::obj_dude; @@ -74,10 +110,14 @@ static void SaveRealDudeState() { realDude.Experience = fo::var::Experience_; realDude.free_perk = fo::var::free_perk; realDude.unspent_skill_points = fo::var::curr_pc_stat[0]; - //real_map_elevation = fo::var::map_elevation; realDude.sneak_working = fo::var::sneak_working; fo::SkillGetTags(realDude.tag_skill, 4); + for (int i = 0; i < 6; i++) realDude.addictGvar[i] = fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar]; + realDude.addictGvar[6] = fo::var::game_global_vars[fo::var::drugInfoList[7].addictGvar]; + realDude.addictGvar[7] = fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar]; + if (realDude.extendAddictGvar) SaveAddictGvarState(); + if (skipCounterAnim) SafeWriteBatch(0, {0x422BDE, 0x4229EC}); // no animate } @@ -100,8 +140,8 @@ static void SetCurrentDude(fo::GameObject* npc) { fo::func::critter_pc_set_name(fo::func::critter_name(npc)); // change level - int level = fo::func::isPartyMember(npc) - ? fo::func::partyMemberGetCurLevel(npc) + int level = fo::func::isPartyMember(npc) + ? fo::func::partyMemberGetCurLevel(npc) : 0; fo::var::Level_ = level; @@ -121,6 +161,16 @@ static void SetCurrentDude(fo::GameObject* npc) { fo::var::itemCurrentItem = 1; } + bool isAddict = false; + for (int i = 0; i < 9; i++) fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = 0; + for (int i = 0; i < 9; i++) { + if (!fo::CheckAddictByPid(npc, fo::var::drugInfoList[i].itemPid)) continue; + fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = 1; + isAddict = true; + } + if (realDude.extendAddictGvar) isAddict |= SetAddictGvar(npc); // check new added addictions + fo::ToggleNpcFlag(npc, 4, isAddict); // for show/hide addiction box (fix bug) + // switch main dude_obj pointers - this should be done last! fo::var::obj_dude = npc; fo::var::inven_dude = npc; @@ -137,6 +187,8 @@ static void SetCurrentDude(fo::GameObject* npc) { static void RestoreRealDudeState() { assert(realDude.obj_dude != nullptr); + fo::var::map_elevation = realDude.obj_dude->elevation; + fo::var::obj_dude = realDude.obj_dude; fo::var::inven_dude = realDude.obj_dude; fo::var::inven_pid = realDude.obj_dude->protoId; @@ -152,7 +204,6 @@ static void RestoreRealDudeState() { fo::var::Experience_ = realDude.Experience; fo::var::free_perk = realDude.free_perk; fo::var::curr_pc_stat[0] = realDude.unspent_skill_points; - //realDude.map_elevation = fo::var::map_elevation; -- why save elevation? fo::var::sneak_working = realDude.sneak_working; fo::SkillSetTags(realDude.tag_skill, 4); @@ -160,6 +211,11 @@ static void RestoreRealDudeState() { fo::func::stat_pc_add_experience(delayedExperience); } + for (int i = 0; i < 6; i++) fo::var::game_global_vars[fo::var::drugInfoList[i].addictGvar] = realDude.addictGvar[i]; + fo::var::game_global_vars[fo::var::drugInfoList[7].addictGvar] = realDude.addictGvar[6]; + fo::var::game_global_vars[fo::var::drugInfoList[8].addictGvar] = realDude.addictGvar[7]; + if (realDude.extendAddictGvar) RestoreAddictGvarState(); + if (skipCounterAnim) SafeWriteBatch(1, {0x422BDE, 0x4229EC}); // restore fo::func::intface_redraw(); @@ -279,12 +335,31 @@ static void __declspec(naked) gdControlUpdateInfo_hook() { } } +static void NpcAutoLevelPatch() { + npcAutoLevelEnabled = GetConfigInt("Misc", "NPCAutoLevel", 0) != 0; + if (npcAutoLevelEnabled) { + dlog("Applying NPC autolevel patch.", DL_INIT); + SafeWrite8(0x495CFB, 0xEB); // jmps 0x495D28 (skip random check) + dlogr(" Done", DL_INIT); + } +} + void PartyControl::init() { - LoadGameHook::OnGameReset() += PartyControlReset; + LoadGameHook::OnGameReset() += []() { + PartyControlReset(); + if (!npcEngineLevelUp) { + npcEngineLevelUp = true; + SafeWrite16(0x4AFC1C, 0x840F); + } + }; + + if (Drugs::addictionGvarCount) realDude.extendAddictGvar = new long[Drugs::addictionGvarCount]; HookCall(0x454218, stat_pc_add_experience_hook); // call inside op_give_exp_points_hook HookCalls(pc_flag_toggle_hook, { 0x4124F1, 0x41279A }); + NpcAutoLevelPatch(); + skipCounterAnim = (GetConfigInt("Misc", "SpeedInterfaceCounterAnims", 0) == 3); // display party member's current level & AC & addict flag @@ -298,4 +373,8 @@ void PartyControl::init() { } } +void PartyControl::exit() { + if (realDude.extendAddictGvar) delete[] realDude.extendAddictGvar; +} + } diff --git a/sfall/Modules/PartyControl.h b/sfall/Modules/PartyControl.h index 5a9861e6..ba66f964 100644 --- a/sfall/Modules/PartyControl.h +++ b/sfall/Modules/PartyControl.h @@ -26,6 +26,7 @@ class PartyControl : public Module { public: const char* name() { return "PartyControl"; } void init(); + void exit() override; static int __stdcall SwitchHandHook(fo::GameObject* item); @@ -39,5 +40,7 @@ public: }; extern bool isControllingNPC; +extern bool npcAutoLevelEnabled; +extern bool npcEngineLevelUp; } diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index cc0b9e8e..ab479dd1 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -29,53 +29,78 @@ namespace sfall using namespace fo; constexpr int maxNameLen = 64; // don't change size -constexpr int maxDescLen = 1024; // don't change size +constexpr int maxDescLen = 512; // don't change size +static const int descLen = 256; // maximum text length for interface -static char perksFile[MAX_PATH]; +static char perksFile[MAX_PATH] = {0}; static char Name[maxNameLen * PERK_count] = {0}; -static char Desc[maxDescLen * PERK_count] = {0}; +static char Desc[descLen * PERK_count] = {0}; static char tName[maxNameLen * TRAIT_count] = {0}; -static char tDesc[maxDescLen * TRAIT_count] = {0}; -static bool disableTraits[TRAIT_count]; +static char tDesc[descLen * TRAIT_count] = {0}; +static char PerkBoxTitle[33]; #define check_trait(id) !disableTraits[id] && (var::pc_trait[0] == id || var::pc_trait[1] == id) +static const int startFakeID = 256; static DWORD addPerkMode = 2; +static bool perksReInit = false; -//static const PerkStruct BlankPerk={ &Name[PERK_count*64], &Desc[PERK_count*1024], 0x48, 1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }; static PerkInfo perks[PERK_count]; static TraitInfo traits[TRAIT_count]; +struct PerkInfoExt { + short id; + char reserve[6]; + char Name[maxNameLen]; + char Desc[descLen]; + PerkInfo data; + // extra modificators + long stat1; + long stat1Mod; + long stat2; + long stat2Mod; + + long skill3; + long skill3Mod; + long skill4; + long skill4Mod; + long skill5; + long skill5Mod; +}; +static std::vector extPerks; + struct FakePerk { - int Level; + int Level; // current level (max 100) int Image; char Name[maxNameLen]; char Desc[maxDescLen]; + char reserve[510]; // empty block + short id; // use last bytes of the description under the ID value for compatibility FakePerk() {} - FakePerk(char* _name, int _level, int _image, char* _desc) : Name {0}, Desc {0} { + FakePerk(char* _name, int _level, int _image, char* _desc) : id(-1), Name {0}, Desc {0}, reserve {0} { Level = _level; Image = _image; - strncpy_s(Name, _name, _TRUNCATE); - strncpy_s(Desc, _desc, _TRUNCATE); + strncpy_s(this->Name, _name, _TRUNCATE); + strncpy_s(this->Desc, _desc, _TRUNCATE); } }; std::vector fakeTraits; std::vector fakePerks; -std::vector fakeSelectablePerks; +std::vector fakeSelectablePerks; // available perks for selection in the perk selection list -static DWORD RemoveTraitID; -static DWORD RemovePerkID; -static DWORD RemoveSelectableID; +static std::list RemoveTraitID; +static std::list RemovePerkID; +static std::list RemoveSelectableID; static DWORD TraitSkillBonuses[TRAIT_count * 18] = {0}; static DWORD TraitStatBonuses[TRAIT_count * (STAT_max_derived + 1)] = {0}; +static bool disableTraits[TRAIT_count]; static DWORD IgnoringDefaultPerks = 0; -static char PerkBoxTitle[33]; static DWORD PerkFreqOverride = 0; @@ -89,6 +114,24 @@ static const DWORD GainStatPerks[7][2] = { {0x4AF24D, 0x90}, // Luck }; +// Returns the index for the found ID +int PerkSearchID(int id) { + int left = 0, + right = extPerks.size(); + if (!--right) return (extPerks[right].id == id) ? right : -1; + while (left <= right) { + int mid = (left + right) / 2; // middle index of the segment + int value = extPerks[mid].id; + if (value == id) return mid; + if (value > id) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return -1; +} + void _stdcall SetPerkFreq(int i) { PerkFreqOverride = i; } @@ -150,260 +193,263 @@ void _stdcall SetPerkboxTitle(char* name) { void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { if (active < 0 || active > 1) return; + size_t size = fakeSelectablePerks.size(); if (active == 0) { - for (DWORD i = 0; i < fakeSelectablePerks.size(); i++) { + for (size_t i = 0; i < size; i++) { if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks.erase(fakeSelectablePerks.begin() + i); return; } } } else { - for (DWORD i = 0; i < fakeSelectablePerks.size(); i++) { + for (size_t i = 0; i < size; i++) { if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks[i].Level = active; fakeSelectablePerks[i].Image = image; - strncpy_s(fakeSelectablePerks[i].Desc, desc, _TRUNCATE); + strncpy(fakeSelectablePerks[i].Desc, desc, descLen - 1); + fakeSelectablePerks[i].Desc[descLen - 1] = 0; return; } } + if (size == fakeSelectablePerks.capacity()) fakeSelectablePerks.reserve(size + 10); fakeSelectablePerks.push_back(FakePerk(name, active, image, desc)); } } void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { if (level < 0 || level > 100) return; - if (level == 0) { - for (DWORD i = 0; i < fakePerks.size(); i++) { + size_t size = fakePerks.size(); + if (level == 0) { // remove perk from fakePerks + for (size_t i = 0; i < size; i++) { if (!strcmp(name, fakePerks[i].Name)) { fakePerks.erase(fakePerks.begin() + i); return; } } - } else { - for (DWORD i = 0; i < fakePerks.size(); i++) { + } else { // add or change the existing fake perk in fakePerks + for (size_t i = 0; i < size; i++) { if (!strcmp(name, fakePerks[i].Name)) { fakePerks[i].Level = level; fakePerks[i].Image = image; - strncpy_s(fakePerks[i].Desc, desc, _TRUNCATE); + strncpy(fakePerks[i].Desc, desc, descLen - 1); + fakePerks[i].Desc[descLen - 1] = 0; return; } } + if (size == fakePerks.capacity()) fakePerks.reserve(size + 10); fakePerks.push_back(FakePerk(name, level, image, desc)); } } void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { if (active < 0 || active > 1) return; + size_t size = fakeTraits.size(); if (active == 0) { - for (DWORD i = 0; i < fakeTraits.size(); i++) { + for (size_t i = 0; i < size; i++) { if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits.erase(fakeTraits.begin() + i); return; } } } else { - for (DWORD i = 0; i < fakeTraits.size(); i++) { + for (size_t i = 0; i < size; i++) { if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits[i].Level = active; fakeTraits[i].Image = image; - strncpy_s(fakeTraits[i].Desc, desc, _TRUNCATE); + strncpy(fakeTraits[i].Desc, desc, descLen - 1); + fakeTraits[i].Desc[descLen - 1] = 0; return; } } + if (size == fakeTraits.capacity()) fakeTraits.reserve(size + 5); fakeTraits.push_back(FakePerk(name, active, image, desc)); } } -static DWORD _stdcall HaveFakeTraits() { - return fakeTraits.size(); -} - static DWORD _stdcall HaveFakePerks() { return fakePerks.size(); } -static FakePerk* _stdcall GetFakePerk2(int id) { +static long __fastcall GetFakePerkLevel(int id) { + return fakePerks[id - PERK_count].Level; +} + +static long __fastcall GetFakePerkImage(int id) { + return fakePerks[id - PERK_count].Image; +} + +static FakePerk* __fastcall GetFakePerk(int id) { return &fakePerks[id - PERK_count]; } -static void __declspec(naked) GetFakePerk() { - __asm { - mov eax, [esp + 4]; - push ecx; - push edx; - push eax; - call GetFakePerk2; - pop edx; - pop ecx; - retn 4; +static DWORD __fastcall GetFakeSelectPerkLevel(int id) { + if (id < startFakeID) { + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (fakePerks[i].id == id) return fakePerks[i].Level; + } + return 0; } -} - -static FakePerk* _stdcall GetFakeSPerk2(int id) { - return &fakeSelectablePerks[id - PERK_count]; -} - -static void __declspec(naked) GetFakeSPerk() { - __asm { - mov eax, [esp + 4]; - push ecx; - push edx; - push eax; - call GetFakeSPerk2; - pop edx; - pop ecx; - retn 4; - } -} - -static DWORD _stdcall GetFakeSPerkLevel2(int id) { - char* name = fakeSelectablePerks[id - PERK_count].Name; + char* name = fakeSelectablePerks[id - startFakeID].Name; for (DWORD i = 0; i < fakePerks.size(); i++) { if (!strcmp(name, fakePerks[i].Name)) return fakePerks[i].Level; } return 0; } -static void __declspec(naked) GetFakeSPerkLevel() { - __asm { - mov eax, [esp + 4]; - push ecx; - push edx; - push eax; - call GetFakeSPerkLevel2; - pop edx; - pop ecx; - retn 4; +static long __fastcall GetFakeSelectPerkImage(int id) { + if (id < startFakeID) { + int i = PerkSearchID(id); + return (i != -1) ? extPerks[i].data.image : i; } + return fakeSelectablePerks[id - startFakeID].Image; } -static DWORD HandleFakeTraits(int value) { +static FakePerk* __fastcall GetFakeSelectPerk(int id) { + if (id < startFakeID) { + int i = PerkSearchID(id); + if (i < 0) i = 0; // if id is not found + return (FakePerk*)&extPerks[i]; + } + return &fakeSelectablePerks[id - startFakeID]; +} + +static DWORD HandleFakeTraits(int isSelect) { for (DWORD i = 0; i < fakeTraits.size(); i++) { - if (fo::func::folder_print_line(fakeTraits[i].Name) && !value) { - value = 1; + if (fo::func::folder_print_line(fakeTraits[i].Name) && !isSelect) { + isSelect = 1; var::folder_card_fid = fakeTraits[i].Image; var::folder_card_title = (DWORD)fakeTraits[i].Name; var::folder_card_title2 = 0; var::folder_card_desc = (DWORD)fakeTraits[i].Desc; } } - return value; + return isSelect; } -static long _fastcall PlayerHasPerk(int* value) { - *value = HandleFakeTraits(*value); +static long __fastcall PlayerHasPerk(int* isSelectPtr) { + *isSelectPtr = HandleFakeTraits(*isSelectPtr); for (int i = 0; i < PERK_count; i++) { - if (fo::func::perk_level(fo::var::obj_dude, i)) return 1; + if (fo::func::perk_level(fo::var::obj_dude, i)) return 0x43438A; // print perks } - return fakePerks.size(); + return (!fakePerks.empty()) ? 0x43438A : 0x434446; // skip print perks +} + +static DWORD __fastcall HaveFakeTraits(int* isSelectPtr) { + return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; } static void __declspec(naked) PlayerHasPerkHack() { __asm { - push ecx; // value - mov ecx, esp; // ptr to value + push ecx; // isSelect + mov ecx, esp; // ptr to isSelect call PlayerHasPerk; pop ecx; // value from HandleFakeTraits - test eax, eax; - jnz win; - mov eax, 0x434446; - jmp eax; -win: - mov eax, 0x43438A; jmp eax; } } static void __declspec(naked) PlayerHasTraitHook() { __asm { - push ecx; + push ecx; // isSelect + mov ecx, esp; // ptr to isSelect call HaveFakeTraits; - pop ecx; - test eax, eax; - jz end; - mov eax, 0x43425B; + pop ecx; // value from HandleFakeTraits jmp eax; -end: - jmp PlayerHasPerkHack; } } static void __declspec(naked) GetPerkLevelHook() { __asm { cmp edx, PERK_count; - jl end; - push edx; - call GetFakePerk; - mov eax, ds:[eax]; - retn; -end: + jge fake; jmp fo::funcoffs::perk_level_; +fake: + push ecx; + mov ecx, edx; + call GetFakePerkLevel; + pop ecx; + retn; } } static void __declspec(naked) GetPerkImageHook() { __asm { cmp eax, PERK_count; - jl end; - push eax; - call GetFakePerk; - mov eax, ds:[eax + 4]; - retn; -end: + jge fake; jmp fo::funcoffs::perk_skilldex_fid_; +fake: + push ecx; + mov ecx, eax; + call GetFakePerkImage; + pop ecx; + retn; } } static void __declspec(naked) GetPerkNameHook() { __asm { cmp eax, PERK_count; - jl end; - push eax; + jge fake; + jmp fo::funcoffs::perk_name_; +fake: + push ecx; + mov ecx, eax; call GetFakePerk; lea eax, ds:[eax + 8]; + pop ecx; retn; -end: - jmp fo::funcoffs::perk_name_; } } static void __declspec(naked) GetPerkDescHook() { __asm { cmp eax, PERK_count; - jl end; - push eax; + jge fake; + jmp fo::funcoffs::perk_description_; +fake: + push ecx; + mov ecx, eax; call GetFakePerk; lea eax, ds:[eax + 72]; + pop ecx; retn; -end: - jmp fo::funcoffs::perk_description_ } } +// Search all available perks for the player to display them in the character screen +static const DWORD EndPerkLoopExit = 0x434446; +static const DWORD EndPerkLoopCont = 0x4343A5; static void __declspec(naked) EndPerkLoopHack() { __asm { - jl cLoop; // if ebx < 119 + jl cLoop; // if ebx < 119 push ecx; - call HaveFakePerks; + call HaveFakePerks; // return perks count (fake + ext) pop ecx; - add eax, PERK_count; - cmp ebx, eax; + add eax, PERK_count; // total = perks count + vanilla count + cmp ebx, eax; // if perkId < total then continue jl cLoop; - mov eax, 0x434446; // exit loop - jmp eax; + jmp EndPerkLoopExit; // exit loop cLoop: - mov eax, 0x4343A5; // continue loop - jmp eax; + jmp EndPerkLoopCont; // continue loop } } -static DWORD _stdcall HandleExtraSelectablePerks(DWORD offset, DWORD* data) { - for (DWORD i = 0; i < fakeSelectablePerks.size(); i++) { - //*(WORD*)(_name_sort_list + (offset+i)*8)=(WORD)(PERK_count+i); - data[offset + i] = PERK_count + i; +// Build a table of perks ID numbers available for selection +// data buffer has limited size for 119 perks +static DWORD _stdcall HandleExtraSelectablePerks(DWORD available, DWORD* data) { + size_t count = extPerks.size(); + for (size_t i = 0; i < count; i++) { + if (available >= 119) break; // exit if the buffer is overfull + if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; } - return offset + fakeSelectablePerks.size(); + count = fakeSelectablePerks.size(); + for (size_t i = 0; i < count; i++) { + if (available >= 119) break; + // for fake perks, their ID should start from 256 + data[available++] = startFakeID + i; //*(WORD*)(_name_sort_list + (offset+i)*8)=(WORD)(PERK_count+i); + } + return available; // total number of perks available for selection } static void __declspec(naked) GetAvailablePerksHook() { @@ -412,13 +458,13 @@ static void __declspec(naked) GetAvailablePerksHook() { push edx; // arg data mov ecx, IgnoringDefaultPerks; test ecx, ecx; - jnz skipdefaults; - call fo::funcoffs::perk_make_list_; + jnz skipDefaults; + call fo::funcoffs::perk_make_list_; // return available count jmp next; -skipdefaults: +skipDefaults: xor eax, eax; next: - push eax; + push eax; // arg available call HandleExtraSelectablePerks; pop ecx; retn; @@ -428,88 +474,166 @@ next: static void __declspec(naked) GetPerkSLevelHook() { __asm { cmp edx, PERK_count; - jl end; - push edx; - call GetFakeSPerkLevel; - retn; -end: + jge fake; jmp fo::funcoffs::perk_level_; +fake: + push ecx; + mov ecx, edx; + call GetFakeSelectPerkLevel; + pop ecx; + retn; } } static void __declspec(naked) GetPerkSImageHook() { __asm { cmp eax, PERK_count; - jl end; - push eax; - call GetFakeSPerk; - mov eax, ds:[eax + 4]; - retn; -end: + jge fake; jmp fo::funcoffs::perk_skilldex_fid_; +fake: + push ecx; + push edx; + mov ecx, eax; + call GetFakeSelectPerkImage; + pop edx; + pop ecx; + retn; } } static void __declspec(naked) GetPerkSNameHook() { __asm { cmp eax, PERK_count; - jl end; - push eax; - call GetFakeSPerk; - lea eax, ds:[eax + 8]; - retn; -end: + jge fake; jmp fo::funcoffs::perk_name_; +fake: + push ecx; + push edx; + mov ecx, eax; + call GetFakeSelectPerk; + lea eax, ds:[eax + 8]; // Name + pop edx; + pop ecx; + retn; } } static void __declspec(naked) GetPerkSDescHook() { __asm { cmp eax, PERK_count; - jl end; - push eax; - call GetFakeSPerk; - lea eax, ds:[eax + 72]; - retn; -end: + jge fake; jmp fo::funcoffs::perk_description_; +fake: + push ecx; + push edx; + mov ecx, eax; + call GetFakeSelectPerk; + lea eax, ds:[eax + 72]; // Desc + pop edx; + pop ecx; + retn; } } +static void PerkSkillMod(fo::GameObject* critter, long skill, long mod, long type) { + if (mod == 0 || skill >= fo::SKILL_count) return; + if (mod < 0) { + type = !type; + mod = -mod; + } + if (fo::func::skill_is_tagged(skill)) mod /= 2; + do { + if (type) { + fo::func::skill_inc_point_force(critter, skill); + } else { + fo::func::skill_dec_point_force(critter, skill); + } + } while (--mod); +} + +static void PerkStatMod(fo::GameObject* critter, long stat, long mod, long type) { + if (mod == 0 || stat > fo::STAT_poison_resist) return; + if (!type) mod = -mod; + int value = fo::func::stat_get_bonus(critter, stat); + fo::func::stat_set_bonus(critter, stat, value + mod); +} + +static void ApplyPerkEffect(long index, fo::GameObject* critter, long type) { + int stat = extPerks[index].stat1; + if (stat > -1) PerkStatMod(critter, stat, extPerks[index].stat1Mod, type); + stat = extPerks[index].stat2; + if (stat > -1) PerkStatMod(critter, stat, extPerks[index].stat2Mod, type); + + int skill = extPerks[index].skill3; + if (skill > -1) PerkSkillMod(critter, skill, extPerks[index].skill3Mod, type); + skill = extPerks[index].skill4; + if (skill > -1) PerkSkillMod(critter, skill, extPerks[index].skill4Mod, type); + skill = extPerks[index].skill5; + if (skill > -1) PerkSkillMod(critter, skill, extPerks[index].skill5Mod, type); +} + +// Adds the selected perk to the player static void _stdcall AddFakePerk(DWORD perkID) { - perkID -= PERK_count; - if (addPerkMode & 1) { - bool matched = false; - for (DWORD d = 0; d < fakeTraits.size(); d++) { + size_t count; + bool matched = false; + if (perkID < startFakeID) { + count = fakePerks.size(); + for (size_t d = 0; d < count; d++) { + if (fakePerks[d].id == perkID) { + RemovePerkID.push_back(d); + fakePerks[d].Level++; + matched = true; + break; + } + } + if (!matched) { // add to fakePerks + RemovePerkID.push_back(count); // index of the added perk + int index = PerkSearchID(perkID); + FakePerk perk(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc); + perk.id = extPerks[index].id; // same as perkID + fakePerks.emplace_back(perk); + } + fo::func::perk_add_effect(fo::var::obj_dude, perkID); + return; + } + // behavior for fake perk/trait + perkID -= startFakeID; + if (addPerkMode & 1) { // add perk to trait + count = fakeTraits.size(); + for (size_t d = 0; d < count; d++) { if (!strcmp(fakeTraits[d].Name, fakeSelectablePerks[perkID].Name)) { matched = true; + break; } } if (!matched) { - RemoveTraitID = fakeTraits.size(); + RemoveTraitID.push_back(count); // index of the added trait fakeTraits.push_back(fakeSelectablePerks[perkID]); } } - if (addPerkMode & 2) { - bool matched = false; - for (DWORD d = 0; d < fakePerks.size(); d++) { + if (addPerkMode & 2) { // default mode + matched = false; + count = fakeTraits.size(); + for (size_t d = 0; d < count; d++) { if (!strcmp(fakePerks[d].Name, fakeSelectablePerks[perkID].Name)) { - RemovePerkID = d; + RemovePerkID.push_back(d); fakePerks[d].Level++; matched = true; + break; } } - if (!matched) { - RemovePerkID = fakePerks.size(); + if (!matched) { // add to fakePerks + RemovePerkID.push_back(count); // index of the added perk fakePerks.push_back(fakeSelectablePerks[perkID]); } } - if (addPerkMode & 4) { - RemoveSelectableID = perkID; + if (addPerkMode & 4) { // delete from selectable perks + RemoveSelectableID.push_back(perkID); //fakeSelectablePerks.remove_at(perkID); } } +// Adds perk from selection window to player static void __declspec(naked) AddPerkHook() { __asm { cmp edx, PERK_count; @@ -526,6 +650,7 @@ normalPerk: pop edx; test eax, eax; jnz end; + // fix gain perks cmp edx, PERK_gain_strength_perk; jl end; cmp edx, PERK_gain_luck_perk; @@ -536,6 +661,92 @@ end: } } +// Checks player statistics to add perk in selection listing +static PerkInfo* __fastcall CanAddPerk(DWORD perkID) { + int index = PerkSearchID(perkID); + if (index != -1) { + int ranks = extPerks[index].data.ranks; + if (ranks > 0) { + for (size_t i = 0; i < fakePerks.size(); i++) { + if (fakePerks[i].id == perkID) { + if (fakePerks[i].Level >= ranks) return 0; // not available to add + break; + } + } + return &extPerks[index].data; + } + } + return 0; +} + +static const DWORD perk_can_add_exit = 0x496A03; +static const DWORD perk_can_add_check = 0x496872; +static void __declspec(naked) perk_can_add_hack() { + __asm { + test edx, edx; + jz end; + push edx; + mov ecx, edx; + call CanAddPerk; + pop edx; + test eax, eax; + jz end; + mov ecx, eax; // ptr to perk_data + jmp perk_can_add_check; +end: + jmp perk_can_add_exit; + } +} + +static PerkInfo* __fastcall PerkData(DWORD perkID, fo::GameObject* critter, long type) { + int index = PerkSearchID(perkID); + if (index != -1) { + ApplyPerkEffect(index, critter, type); // apply ext. perk to critter + return &extPerks[index].data; + } + return 0; +} + +static const DWORD perk_add_effect_exit = 0x496CD9; +static const DWORD perk_add_effect_continue = 0x496C4A; +static void __declspec(naked) perk_add_effect_hook() { + __asm { + cmp edx, startFakeID; + jge end; + push ecx; + push 1; // add effect + mov edx, esi; // critter + call PerkData; // ecx - perkID + pop ecx; + test eax, eax; + jz end; + mov edi, eax; // ptr to perk_data + jmp perk_add_effect_continue; +end: + jmp perk_add_effect_exit; + } +} + +static const DWORD perk_remove_effect_exit = 0x496D99; +static const DWORD perk_remove_effect_continue = 0x496D2E; +static void __declspec(naked) perk_remove_effect_hook() { + __asm { + cmp edx, startFakeID; + jge end; + push ecx; + push 0; // remove effect + mov edx, esi; // critter + call PerkData; // ecx - perkID + pop ecx; + test eax, eax; + jz end; + mov edi, eax; // ptr to perk_data + jmp perk_remove_effect_continue; +end: + jmp perk_remove_effect_exit; + } +} + static void __declspec(naked) HeaveHoHook() { __asm { xor edx, edx; @@ -567,9 +778,9 @@ void _stdcall ApplyHeaveHoFix() { // not really a fix perkHeaveHoModFix = true; } -static void PerkSetup() { +static void PerkEngineInit() { // Character screen (list_perks_) - HookCall(0x434256, PlayerHasTraitHook); // jz + HookCall(0x434256, PlayerHasTraitHook); // jz func MakeJump(0x43436B, PlayerHasPerkHack); HookCall(0x4343AC, GetPerkLevelHook); HookCall(0x43440D, GetPerkImageHook); @@ -599,15 +810,22 @@ static void PerkSetup() { MakeCall(0x4AFB2F, LevelUpHack, 1); // replaces 'mov edx, ds:[PlayerLevel]' SafeWrite8(0x43C2EC, 0xEB); // skip the block of code which checks if the player has gained a perk (now handled in level up code) - memcpy(perks, var::perk_data, sizeof(PerkInfo) * PERK_count); + // Disable losing unused perks + SafeWrite16(0x43C369, 0x0DFE); // dec byte ptr ds:_free_perk + SafeWrite8(0x43C370, 0xB1); // jmp 0x43C322 +} - // _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); +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); + } if (perksFile[0] != '\0') { char num[4]; for (int i = 0; i < PERK_count; i++) { @@ -615,8 +833,8 @@ static void PerkSetup() { if (GetPrivateProfileString(num, "Name", "", &Name[i * maxNameLen], maxNameLen - 1, perksFile)) { perks[i].name = &Name[i * maxNameLen]; } - if (GetPrivateProfileString(num, "Desc", "", &Desc[i * maxDescLen], maxDescLen - 1, perksFile)) { - perks[i].description = &Desc[i * maxDescLen]; + if (GetPrivateProfileString(num, "Desc", "", &Desc[i * descLen], descLen - 1, perksFile)) { + perks[i].description = &Desc[i * descLen]; } int value; value = GetPrivateProfileInt(num, "Image", -99999, perksFile); @@ -654,22 +872,66 @@ static void PerkSetup() { value = GetPrivateProfileInt(num, "LCK", -99999, perksFile); if (value != -99999) perks[i].luckMin = value; } - } - - for (int i = 0; i < PERK_count; i++) { - if (perks[i].name != &Name[maxNameLen * i]) { - strcpy_s(&Name[maxNameLen * i], maxNameLen, perks[i].name); - perks[i].name = &Name[maxNameLen * i]; + if (perksReInit) { + perksReInit = false; + return; } - if (perks[i].description && perks[i].description != &Desc[maxDescLen * i]) { - strcpy_s(&Desc[maxDescLen * i], maxDescLen, perks[i].description); - perks[i].description = &Desc[maxDescLen * i]; + // adding extra perks with IDs from 119 to 255 + extPerks.reserve(startFakeID - PERK_count); + extPerks.resize(1); + int n = 0; + for (int id = PERK_count; id < startFakeID; id++) { + _itoa(id, num, 10); + int ranks = GetPrivateProfileInt(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); + + GetPrivateProfileString(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); + 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].id = id; + ++n; + extPerks.resize(n + 1); // add next 'empty' perk + } + extPerks.pop_back(); + extPerks.shrink_to_fit(); + + if (!extPerks.empty()) { + MakeJump(0x496823, perk_can_add_hack); + HookCall(0x496C2D, perk_add_effect_hook); // jge func + HookCall(0x496D11, perk_remove_effect_hook); // jge func + dlog_f("Added extra %d perks\n", DL_INIT, n); } } - - // Disable losing unused perks - SafeWrite16(0x43C369, 0x0DFE); // dec byte ptr ds:_free_perk - SafeWrite8(0x43C370, 0xB1); // jmp 0x43C322 + perksReInit = false; } static __declspec(naked) void PerkInitWrapper() { @@ -684,6 +946,8 @@ static __declspec(naked) void PerkInitWrapper() { } } +/////////////////////////// TRAIT FUNCTIONS /////////////////////////////////// + static int stat_get_base_direct(DWORD statID) { return fo::func::stat_get_base_direct(fo::var::obj_dude, statID); } @@ -830,8 +1094,8 @@ static void TraitSetup() { if (GetPrivateProfileString(num, "Name", "", &tName[i * maxNameLen], maxNameLen - 1, perksFile)) { traits[i].name = &tName[i * maxNameLen]; } - if (GetPrivateProfileString(num, "Desc", "", &tDesc[i * maxDescLen], maxDescLen - 1, perksFile)) { - traits[i].description = &tDesc[i * maxDescLen]; + if (GetPrivateProfileString(num, "Desc", "", &tDesc[i * descLen], descLen - 1, perksFile)) { + traits[i].description = &tDesc[i * descLen]; } int value; value = GetPrivateProfileInt(num, "Image", -99999, perksFile); @@ -864,52 +1128,41 @@ static void TraitSetup() { if (GetPrivateProfileInt(num, "NoHardcode", 0, perksFile)) { disableTraits[i] = true; switch (i) { - case 3: + case TRAIT_one_hander: HookCall(0x4245E0, BlockedTrait); break; - case 4: + case TRAIT_finesse: HookCall(0x4248F9, BlockedTrait); break; - case 7: + case TRAIT_fast_shot: HookCall(0x478C8A, BlockedTrait); // fast shot HookCall(0x478E70, BlockedTrait); break; - case 8: + case TRAIT_bloody_mess: HookCall(0x410707, BlockedTrait); break; - case 9: + case TRAIT_jinxed: HookCall(0x42389F, BlockedTrait); break; - case 11: + case TRAIT_drug_addict: HookCall(0x47A0CD, BlockedTrait); HookCall(0x47A51A, BlockedTrait); break; - case 12: + case TRAIT_drug_resistant: HookCall(0x479BE1, BlockedTrait); HookCall(0x47A0DD, BlockedTrait); break; - case 14: + case TRAIT_skilled: HookCall(0x43C295, BlockedTrait); HookCall(0x43C2F3, BlockedTrait); break; - case 15: + case TRAIT_gifted: HookCall(0x43C2A4, BlockedTrait); break; } } } } - - for (int i = 0; i < TRAIT_count; i++) { - if (traits[i].name != &tName[maxNameLen * i]) { - strcpy_s(&tName[maxNameLen * i], maxNameLen, traits[i].name); - traits[i].name = &tName[maxNameLen * i]; - } - if (traits[i].description && traits[i].description != &tDesc[maxDescLen * i]) { - strcpy_s(&tDesc[maxDescLen * i], maxDescLen, traits[i].description); - traits[i].description = &tDesc[maxDescLen * i]; - } - } } static __declspec(naked) void TraitInitWrapper() { @@ -924,20 +1177,26 @@ static __declspec(naked) void TraitInitWrapper() { } } +/////////////////////////////////////////////////////////////////////////////// + void _stdcall SetPerkValue(int id, int value, DWORD offset) { if (id < 0 || id >= PERK_count) return; *(DWORD*)((DWORD)(&perks[id]) + offset) = value; + perksReInit = true; } void _stdcall SetPerkName(int id, char* value) { if (id < 0 || id >= PERK_count) return; strncpy_s(&Name[id * maxNameLen], maxNameLen, value, _TRUNCATE); + perks[id].name = &Name[maxNameLen * id]; + perksReInit = true; } void _stdcall SetPerkDesc(int id, char* value) { if (id < 0 || id >= PERK_count) return; - strncpy_s(&Desc[id * maxDescLen], maxDescLen, value, _TRUNCATE); - perks[id].description = &Desc[maxDescLen * id]; + strncpy_s(&Desc[id * descLen], descLen, value, _TRUNCATE); + perks[id].description = &Desc[descLen * id]; + perksReInit = true; } void PerksReset() { @@ -949,6 +1208,7 @@ void PerksReset() { PerkFreqOverride = 0; if (PerkBoxTitle[0] != 0) { + PerkBoxTitle[0] = 0; SafeWrite32(0x43C77D, 0x488CB); } @@ -963,6 +1223,7 @@ void PerksReset() { SafeWrite32(0x478AC5, 0x23); perkHeaveHoModFix = false; } + if (perksReInit) PerkSetup(); // restore perk data } void Perks::save(HANDLE file) { @@ -987,6 +1248,7 @@ bool Perks::load(HANDLE file) { DWORD count, size; ReadFile(file, &count, 4, &size, 0); if (size != 4) return false; + if (count > 2) fakeTraits.reserve(count); for (DWORD i = 0; i < count; i++) { FakePerk fp; ReadFile(file, &fp, sizeof(FakePerk), &size, 0); @@ -994,12 +1256,21 @@ bool Perks::load(HANDLE file) { } ReadFile(file, &count, 4, &size, 0); if (size != 4) return false; + if (count > 2) fakePerks.reserve(count); for (DWORD i = 0; i < count; i++) { FakePerk fp; ReadFile(file, &fp, sizeof(FakePerk), &size, 0); + if (fp.id > 0) { // update name/desc for non-fake perks + int index = PerkSearchID(fp.id); + if (index != -1) { + strncpy_s(fp.Name, extPerks[index].Name, _TRUNCATE); + strncpy_s(fp.Desc, extPerks[index].Desc, _TRUNCATE); + } + } fakePerks.push_back(fp); } ReadFile(file, &count, 4, &size, 0); + if (count > 2) fakeSelectablePerks.reserve(count); if (size != 4) return false; for (DWORD i = 0; i < count; i++) { FakePerk fp; @@ -1013,16 +1284,22 @@ void _stdcall AddPerkMode(DWORD mode) { addPerkMode = mode; } -DWORD _stdcall HasFakePerk(char* name) { +DWORD HasFakePerk(const char* name, long id) { + if (id < PERK_count && name[0] == 0) return 0; for (DWORD i = 0; i < fakePerks.size(); i++) { - if (!strcmp(name, fakePerks[i].Name)) { - return fakePerks[i].Level; + if (id) { + if (fakePerks[i].id == id) return fakePerks[i].Level; + } else { + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; // current perk level + } } } return 0; } -DWORD _stdcall HasFakeTrait(char* name) { +DWORD _stdcall HasFakeTrait(const char* name) { + if (name[0] == 0) return 0; for (DWORD i = 0; i < fakeTraits.size(); i++) { if (!strcmp(name, fakeTraits[i].Name)) { return 1; @@ -1039,33 +1316,46 @@ void _stdcall ClearSelectablePerks() { } void PerksEnterCharScreen() { - RemoveTraitID = -1; - RemovePerkID = -1; - RemoveSelectableID = -1; + RemoveTraitID.clear(); + RemovePerkID.clear(); + RemoveSelectableID.clear(); } void PerksCancelCharScreen() { - if (RemoveTraitID != -1) { - fakeTraits.erase(fakeTraits.begin() + RemoveTraitID); + if (RemoveTraitID.size() > 1) RemoveTraitID.sort(); + while (!RemoveTraitID.empty()) { + fakeTraits.erase(fakeTraits.begin() + RemoveTraitID.back()); + RemoveTraitID.pop_back(); } - if (RemovePerkID != -1) { - if (!--fakePerks[RemovePerkID].Level) fakePerks.erase(fakePerks.begin() + RemovePerkID); + if (RemovePerkID.size() > 1) RemovePerkID.sort(); // sorting to correctly remove from the end + while (!RemovePerkID.empty()) { + int index = RemovePerkID.back(); + if (!--fakePerks[index].Level) fakePerks.erase(fakePerks.begin() + index); + RemovePerkID.pop_back(); } } void PerksAcceptCharScreen() { - if (RemoveSelectableID != -1) fakeSelectablePerks.erase(fakeSelectablePerks.begin() + RemoveSelectableID); + if (RemoveSelectableID.size() > 1) { + RemoveSelectableID.sort(); + RemoveSelectableID.unique(); + } + while (!RemoveSelectableID.empty()) { + fakeSelectablePerks.erase(fakeSelectablePerks.begin() + RemoveSelectableID.back()); + RemoveSelectableID.pop_back(); + } } void Perks::init() { 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_ - } else perksFile[0] = 0; + } LoadGameHook::OnGameReset() += PerksReset; } diff --git a/sfall/Modules/Perks.h b/sfall/Modules/Perks.h index 9f871b84..4af5f7f8 100644 --- a/sfall/Modules/Perks.h +++ b/sfall/Modules/Perks.h @@ -50,8 +50,8 @@ void _stdcall IgnoreDefaultPerks(); void _stdcall RestoreDefaultPerks(); void _stdcall AddPerkMode(DWORD mode); -DWORD _stdcall HasFakePerk(char* name); -DWORD _stdcall HasFakeTrait(char* name); +DWORD HasFakePerk(const char* name, long id); +DWORD _stdcall HasFakeTrait(const char* name); void _stdcall ClearSelectablePerks(); void _stdcall SetPerkFreq(int i); diff --git a/sfall/Modules/Scripting/Arrays.cpp b/sfall/Modules/Scripting/Arrays.cpp index 23d47e58..aa5f8785 100644 --- a/sfall/Modules/Scripting/Arrays.cpp +++ b/sfall/Modules/Scripting/Arrays.cpp @@ -243,7 +243,7 @@ bool LoadArrays(HANDLE h) { sArrayVar arrayVar; for (DWORD i = 0; i < count; i++) { if (LoadArrayElement(&arrayVar.key, h)) return true; - if (static_cast(arrayVar.key.type) > 4 || arrayVar.key.intVal == 0) { // partial compatibility with 3.4 + if (arrayVar.key.intVal == 0 || static_cast(arrayVar.key.type) >= 4) { // partial compatibility with 3.4 arrayVar.key.intVal = static_cast(arrayVar.key.type); arrayVar.key.type = DataType::INT; } diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index 322e9815..41138b72 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -145,10 +145,13 @@ typedef ArraysMap::iterator array_itr; typedef std::pair array_pair; // auto-incremented ID -extern DWORD nextarrayid; +extern DWORD nextArrayID; + extern DWORD arraysBehavior; + // temp arrays: set of arrayId extern std::set tempArrays; + // saved arrays: arrayKey => arrayId extern ArrayKeysMap savedArrays; diff --git a/sfall/Modules/Scripting/Handlers/Graphics.cpp b/sfall/Modules/Scripting/Handlers/Graphics.cpp index d49d2a42..34c92161 100644 --- a/sfall/Modules/Scripting/Handlers/Graphics.cpp +++ b/sfall/Modules/Scripting/Handlers/Graphics.cpp @@ -224,7 +224,7 @@ void __declspec(naked) op_set_shader_int() { mov edi, eax; mov eax, ecx; call fo::funcoffs::interpretPopLong_; - mov[esp + 8], eax; + mov [esp + 8], eax; mov eax, ecx; call fo::funcoffs::interpretPopShort_; mov edx, eax; @@ -236,7 +236,7 @@ void __declspec(naked) op_set_shader_int() { mov esi, eax; mov eax, ecx; call fo::funcoffs::interpretPopLong_; - mov[esp], eax; + mov [esp], eax; //Error check cmp di, VAR_TYPE_INT; jnz fail; @@ -250,7 +250,7 @@ next: mov eax, ecx; mov ebx, ebp; call fo::funcoffs::interpretGetString_; - mov[esp + 4], eax; + mov [esp + 4], eax; call SetShaderInt; jmp end; fail: @@ -281,7 +281,7 @@ void __declspec(naked) op_set_shader_texture() { mov edi, eax; mov eax, ecx; call fo::funcoffs::interpretPopLong_; - mov[esp + 8], eax; + mov [esp + 8], eax; mov eax, ecx; call fo::funcoffs::interpretPopShort_; mov edx, eax; @@ -293,7 +293,7 @@ void __declspec(naked) op_set_shader_texture() { mov esi, eax; mov eax, ecx; call fo::funcoffs::interpretPopLong_; - mov[esp], eax; + mov [esp], eax; //Error check cmp di, VAR_TYPE_INT; jnz fail; @@ -307,7 +307,7 @@ next: mov eax, ecx; mov ebx, ebp; call fo::funcoffs::interpretGetString_; - mov[esp + 4], eax; + mov [esp + 4], eax; call SetShaderTexture; jmp end; fail: @@ -338,7 +338,7 @@ void __declspec(naked) op_set_shader_float() { mov edi, eax; mov eax, ecx; call fo::funcoffs::interpretPopLong_; - mov[esp + 8], eax; + mov [esp + 8], eax; mov eax, ecx; call fo::funcoffs::interpretPopShort_; mov edx, eax; @@ -350,14 +350,14 @@ void __declspec(naked) op_set_shader_float() { mov esi, eax; mov eax, ecx; call fo::funcoffs::interpretPopLong_; - mov[esp], eax; + mov [esp], eax; //Error check cmp di, VAR_TYPE_FLOAT; jz paramWasFloat; cmp di, VAR_TYPE_INT; jnz fail; - fild[esp + 8]; - fstp[esp + 8]; + fild [esp + 8]; + fstp [esp + 8]; paramWasFloat: cmp dx, VAR_TYPE_STR2; jz next; @@ -369,7 +369,7 @@ next: mov eax, ecx; mov ebx, ebp; call fo::funcoffs::interpretGetString_; - mov[esp + 4], eax; + mov [esp + 4], eax; call SetShaderFloat; jmp end; fail: @@ -398,35 +398,37 @@ void __declspec(naked) op_set_shader_vector() { argloopstart: mov eax, ebp; call fo::funcoffs::interpretPopShort_; - mov word ptr[esp + ecx * 2 + 0x16], ax; + mov word ptr [esp + ecx * 2 + 0x16], ax; mov eax, ebp; call fo::funcoffs::interpretPopLong_; - mov[esp + ecx * 4 - 0x4], eax; - loop argloopstart; + mov [esp + ecx * 4 - 0x4], eax; + dec ecx; + jnz argloopstart; //Error check mov ecx, 4; checkloopstart: - cmp word ptr[esp + ecx * 2 + 0x1a], VAR_TYPE_FLOAT; + cmp word ptr [esp + ecx * 2 + 0x1a], VAR_TYPE_FLOAT; jz paramWasFloat; - cmp word ptr[esp + ecx * 2 + 0x1a], VAR_TYPE_INT; + cmp word ptr [esp + ecx * 2 + 0x1a], VAR_TYPE_INT; jnz fail; - fild[esp + ecx * 4 + 0x4]; - fstp[esp + ecx * 4 + 0x4]; + fild [esp + ecx * 4 + 0x4]; + fstp [esp + ecx * 4 + 0x4]; paramWasFloat: - loop checkloopstart; - cmp word ptr[esp + 0x1a], VAR_TYPE_STR2; + dec ecx; + jnz checkloopstart; + cmp word ptr [esp + 0x1a], VAR_TYPE_STR2; jz next; - cmp word ptr[esp + 0x1a], VAR_TYPE_STR; + cmp word ptr [esp + 0x1a], VAR_TYPE_STR; jnz fail; next: - cmp word ptr[esp + 0x18], VAR_TYPE_INT; + cmp word ptr [esp + 0x18], VAR_TYPE_INT; jnz fail; mov eax, ebp; mov ebx, [esp + 4]; xor edx, edx; - mov dx, word ptr[esp + 0x1a]; + mov dx, word ptr [esp + 0x1a]; call fo::funcoffs::interpretGetString_; - mov[esp + 4], eax; + mov [esp + 4], eax; call SetShaderVector; add esp, 0x12; jmp end; diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index b4d54876..d963eaff 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -303,6 +303,12 @@ end: } } +void sf_add_iface_tag(OpcodeContext &ctx) { + int result = BarBoxes::AddExtraBox(); + if (result == -1) ctx.printOpcodeError("add_iface_tag() - cannot add new tag as the maximum limit of 126 tags has been reached."); + ctx.setReturn(result); +} + void sf_show_iface_tag(OpcodeContext &ctx) { int tag = ctx.arg(0).asInt(); if (tag == 3 || tag == 4) { @@ -396,10 +402,10 @@ void sf_display_stats(OpcodeContext& ctx) { void sf_set_iface_tag_text(OpcodeContext& ctx) { int boxTag = ctx.arg(0).asInt(); - int maxBox = BarBoxes::MaxBox(); + if (boxTag > 4 && boxTag <= maxBox) { - BarBoxes::SetText(boxTag, ctx.arg(1).asString(), ctx.arg(2).asInt()); + BarBoxes::SetText(boxTag, ctx.arg(1).strValue(), ctx.arg(2).asInt()); } else { ctx.printOpcodeError("set_iface_tag_text() - tag value must be in the range of 5 to %d.", maxBox); } diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index 3b2700d1..260463c6 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -67,6 +67,8 @@ void __declspec() op_set_viewport_x(); void __declspec() op_set_viewport_y(); +void sf_add_iface_tag(OpcodeContext&); + void sf_show_iface_tag(OpcodeContext&); void sf_hide_iface_tag(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index aeca9081..ba9df558 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -78,6 +78,7 @@ static const SfallMetarule* currentMetarule; - arg1, arg2, ... - argument types for automatic validation */ static const SfallMetarule metarules[] = { + {"add_iface_tag", sf_add_iface_tag, 0, 0}, {"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}, @@ -109,12 +110,14 @@ static const SfallMetarule metarules[] = { {"item_weight", sf_item_weight, 1, 1, {ARG_OBJECT}}, {"lock_is_jammed", sf_lock_is_jammed, 1, 1, {ARG_OBJECT}}, {"loot_obj", sf_get_loot_object, 0, 0}, + {"npc_engine_level_up", sf_npc_engine_level_up, 1, 1, {ARG_ANY}}, {"obj_under_cursor", sf_get_obj_under_cursor, 2, 2, {ARG_INT, ARG_INT}}, {"outlined_object", sf_outlined_object, 0, 0}, {"real_dude_obj", sf_real_dude_obj, 0, 0}, {"set_can_rest_on_map", sf_set_rest_on_map, 3, 3, {ARG_INT, ARG_INT, ARG_INT}}, {"set_car_intface_art", sf_set_car_intface_art, 1, 1, {ARG_INT}}, {"set_cursor_mode", sf_set_cursor_mode, 1, 1, {ARG_INT}}, + {"set_drugs_data", sf_set_drugs_data, 3, 3, {ARG_INT, ARG_INT, ARG_INT}}, {"set_dude_obj", sf_set_dude_obj, 1, 1, {ARG_OBJECT}}, {"set_flags", sf_set_flags, 2, 2, {ARG_OBJECT, ARG_INT}}, {"set_iface_tag_text", sf_set_iface_tag_text, 3, 3, {ARG_INT, ARG_STRING, ARG_INT}}, @@ -128,38 +131,11 @@ 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}}, + #ifndef NDEBUG {"validate_test", sf_test, 2, 5, {ARG_INT, ARG_NUMBER, ARG_STRING, ARG_OBJECT, ARG_ANY}}, + #endif }; - -static std::string sf_test_stringBuf; -void sf_test(OpcodeContext& ctx) { - std::ostringstream sstream; - sstream << "sfall_funcX(\"test\""; - for (int i = 0; i < ctx.numArgs(); i++) { - const ScriptValue &arg = ctx.arg(i); - sstream << ", "; - switch (arg.type()) { - case DataType::INT: - sstream << arg.asInt(); - break; - case DataType::FLOAT: - sstream << arg.asFloat(); - break; - case DataType::STR: - sstream << '"' << arg.asString() << '"'; - break; - default: - sstream << "???"; - break; - } - } - sstream << ")"; - - sf_test_stringBuf = sstream.str(); - ctx.setReturn(sf_test_stringBuf.c_str()); -} - // returns current contents of metarule table static void sf_get_metarule_table(OpcodeContext& ctx) { DWORD arrId = TempArray(metaruleTable.size(), 0); @@ -218,5 +194,35 @@ void HandleMetarule(OpcodeContext& ctx) { } } +#ifndef NDEBUG +static std::string sf_test_stringBuf; +void sf_test(OpcodeContext& ctx) { + std::ostringstream sstream; + sstream << "sfall_funcX(\"test\""; + for (int i = 0; i < ctx.numArgs(); i++) { + const ScriptValue &arg = ctx.arg(i); + sstream << ", "; + switch (arg.type()) { + case DataType::INT: + sstream << arg.asInt(); + break; + case DataType::FLOAT: + sstream << arg.asFloat(); + break; + case DataType::STR: + sstream << '"' << arg.asString() << '"'; + break; + default: + sstream << "???"; + break; + } + } + sstream << ")"; + + sf_test_stringBuf = sstream.str(); + ctx.setReturn(sf_test_stringBuf.c_str()); +} +#endif + } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.h b/sfall/Modules/Scripting/Handlers/Metarule.h index 925a27ff..d864844e 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.h +++ b/sfall/Modules/Scripting/Handlers/Metarule.h @@ -31,7 +31,9 @@ namespace script { // Example handler. Feel free to add handlers in other files. +#ifndef NDEBUG void sf_test(OpcodeContext&); +#endif // returns current contents of metarule table void sf_get_metarule_table(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index 623cc81d..62967ac2 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -25,9 +25,10 @@ #include "..\..\HeroAppearance.h" #include "..\..\Inventory.h" #include "..\..\KillCounter.h" -#include "..\..\MiscPatches.h" +//#include "..\..\MiscPatches.h" #include "..\..\Movies.h" #include "..\..\Objects.h" +#include "..\..\PartyControl.h" #include "..\..\PlayerModel.h" #include "..\..\ScriptExtender.h" #include "..\..\Stats.h" @@ -201,36 +202,6 @@ void __declspec(naked) op_game_loaded() { } } -void __declspec(naked) op_set_map_time_multi() { - __asm { - push ebx; - push ecx; - push edx; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_FLOAT; - jz paramWasFloat; - cmp dx, VAR_TYPE_INT; - jnz fail; - push eax; - fild dword ptr [esp]; - fstp dword ptr [esp]; - jmp end; -paramWasFloat: - push eax; -end: - call SetMapMulti; -fail: - pop edx; - pop ecx; - pop ebx; - retn; - } -} - void __declspec(naked) op_set_pipboy_available() { __asm { push ebx; @@ -550,44 +521,51 @@ void __declspec(naked) op_eax_available() { } } -static char* NPCToInc; -static void _stdcall IncNPCLevel4(char* npc) { - if (_stricmp(npc, NPCToInc)) { - SafeWrite8(0x495C50, 0xE9); - SafeWrite32(0x495C51, 0x000001FC); - } else { - SafeWrite16(0x495C50, 0x840F); //Want to keep this check intact. - SafeWrite32(0x495C52, 0x000001FB); +static const char* nameNPCToInc; +static long pidNPCToInc; +static bool onceNpcLoop; - //SafeMemSet(0x495C50, 0x90, 6); - SafeMemSet(0x495C77, 0x90, 6); //Check that the player is high enough for the npc to consider this level - //SafeMemSet(0x495C8C, 0x90, 6); //Check that the npc isn't already at its maximum level - SafeMemSet(0x495CEC, 0x90, 6); //Check that the npc hasn't already levelled up recently +static void __cdecl IncNPCLevel(const char* fmt, const char* name) { + fo::GameObject* mObj; + __asm { + push edx; + mov eax, [ebp + 0x150 - 0x1C + 16]; // ebp <- esp + mov edx, [eax]; + mov mObj, edx; + } + + if ((pidNPCToInc && (mObj && mObj->protoId == pidNPCToInc)) || (!pidNPCToInc && !_stricmp(name, nameNPCToInc))) { + fo::func::debug_printf(fmt, name); + + SafeWrite32(0x495C50, 0x01FB840F); // Want to keep this check intact. (restore) + + SafeMemSet(0x495C77, 0x90, 6); // Check that the player is high enough for the npc to consider this level + //SafeMemSet(0x495C8C, 0x90, 6); // Check that the npc isn't already at its maximum level + SafeMemSet(0x495CEC, 0x90, 6); // Check that the npc hasn't already levelled up recently if (!npcAutoLevelEnabled) { - SafeMemSet(0x495D22, 0x90, 6); //Random element + SafeWrite8(0x495CFB, 0xEB); // Disable random element + } + __asm mov [ebp + 0x150 - 0x28 + 16], 255; // set counter for exit loop + } else { + if (!onceNpcLoop) { + SafeWrite32(0x495C50, 0x01FCE9); // set goto next member + onceNpcLoop = true; } } + __asm pop edx; } -static void __declspec(naked) IncNPCLevel3() { - __asm { - pushad; - push eax; - call IncNPCLevel4; - popad; - push 0x495BF9; - retn; - } -} +void sf_inc_npc_level(OpcodeContext& ctx) { + nameNPCToInc = ctx.arg(0).asString(); + pidNPCToInc = ctx.arg(0).asInt(); // set to 0 if passing npc name + if (pidNPCToInc == 0 && nameNPCToInc[0] == 0) return; -static void _stdcall IncNPCLevel2(char* npc) { - NPCToInc = npc; - MakeJump(0x495BEB, IncNPCLevel3); //Replace the debug output with a jmp - __asm { - call fo::funcoffs::partyMemberIncLevels_; - } - SafeWrite16(0x495C50, 0x840F); - SafeWrite32(0x495C52, 0x000001FB); + MakeCall(0x495BF1, IncNPCLevel); // Replace the debug output + __asm call fo::funcoffs::partyMemberIncLevels_; + onceNpcLoop = false; + + // restore code + SafeWrite32(0x495C50, 0x01FB840F); SafeWrite16(0x495C77, 0x8C0F); SafeWrite32(0x495C79, 0x000001D4); //SafeWrite16(0x495C8C, 0x8D0F); @@ -595,112 +573,50 @@ static void _stdcall IncNPCLevel2(char* npc) { SafeWrite16(0x495CEC, 0x850F); SafeWrite32(0x495CEE, 0x00000130); if (!npcAutoLevelEnabled) { - SafeWrite16(0x495D22, 0x8F0F); - SafeWrite32(0x495D24, 0x00000129); + SafeWrite8(0x495CFB, 0x74); } } -void __declspec(naked) op_inc_npc_level() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push eax; - call IncNPCLevel2; -end: - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } -} +void sf_get_npc_level(OpcodeContext& ctx) { + int level = -1; + DWORD findPid = ctx.arg(0).asInt(); // set to 0 if passing npc name + const char *critterName, *name = ctx.arg(0).asString(); -static int _stdcall get_npc_level2(DWORD numMembers, DWORD maxMembers, DWORD* members, DWORD* pids, DWORD* words, const char* name) { - for (DWORD i = 0; i < numMembers; i++) { - const char* name2; - __asm { - mov eax, members; - mov eax, [eax]; - call fo::funcoffs::critter_name_ - mov name2, eax; - } - if (_stricmp(name, name2)) { + if (findPid || name[0] != 0) { + DWORD pid = 0; + DWORD* members = fo::var::partyMemberList; + for (DWORD i = 0; i < fo::var::partyMemberCount; i++) { + if (!findPid) { + __asm { + mov eax, members; + mov eax, [eax]; + call fo::funcoffs::critter_name_; + mov critterName, eax; + } + if (!_stricmp(name, critterName)) { // found npc + pid = ((fo::GameObject*)*members)->protoId; + break; + } + } else { + DWORD _pid = ((fo::GameObject*)*members)->protoId; + if (findPid == _pid) { + pid = _pid; + break; + } + } members += 4; - continue; } - - DWORD pid = ((DWORD*)members[0])[25]; - DWORD id = -1; - for (DWORD j = 0; j < maxMembers; j++) { - if (pids[j] == pid) { - id = j; - break; + if (pid) { + DWORD* pids = fo::var::partyMemberPidList; + for (DWORD j = 0; j < fo::var::partyMemberMaxCount; j++) { + if (pids[j] == pid) { + level = fo::var::partyMemberLevelUpInfoList[j * 3]; + break; + } } } - if (id == -1) break; - - return words[id * 3]; - } - return 0; -} - -void __declspec(naked) op_get_npc_level() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz fail; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push eax; - push ds:[FO_VAR_partyMemberLevelUpInfoList]; - push ds:[FO_VAR_partyMemberPidList]; - push ds:[FO_VAR_partyMemberList]; - push ds:[FO_VAR_partyMemberMaxCount]; - push ds:[FO_VAR_partyMemberCount]; - call get_npc_level2; - mov edx, eax; - jmp end; -fail: - xor edx, edx; -end: - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov eax, edi; - mov edx, VAR_TYPE_INT; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; } + ctx.setReturn(level); } static int ParseIniSetting(const char* iniString, const char* &key, char section[], char file[]) { @@ -732,7 +648,7 @@ static char IniStrBuffer[128]; static DWORD _stdcall GetIniSetting2(const char* c, DWORD string) { const char* key; char section[33], file[67]; - + if (ParseIniSetting(c, key, section, file) < 0) { return -1; } @@ -1701,5 +1617,15 @@ void sf_get_ini_section(OpcodeContext& ctx) { ctx.setReturn(arrayId); } +void sf_npc_engine_level_up(OpcodeContext& ctx) { + if (ctx.arg(0).asBool()) { + if (!npcEngineLevelUp) SafeWrite16(0x4AFC1C, 0x840F); // enable + npcEngineLevelUp = true; + } else { + if (npcEngineLevelUp) SafeWrite16(0x4AFC1C, 0xE990); + npcEngineLevelUp = false; + } +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Misc.h b/sfall/Modules/Scripting/Handlers/Misc.h index 42803fc7..27425288 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.h +++ b/sfall/Modules/Scripting/Handlers/Misc.h @@ -41,8 +41,6 @@ void sf_get_year(OpcodeContext&); void __declspec() op_game_loaded(); -void __declspec() op_set_map_time_multi(); - void __declspec() op_set_pipboy_available(); // Kill counters @@ -72,9 +70,9 @@ void __declspec() op_toggle_active_hand(); void __declspec() op_eax_available(); -void __declspec() op_inc_npc_level(); +void sf_inc_npc_level(OpcodeContext&); -void __declspec() op_get_npc_level(); +void sf_get_npc_level(OpcodeContext&); void __declspec() op_get_ini_setting(); @@ -165,5 +163,7 @@ void sf_get_ini_sections(OpcodeContext&); void sf_get_ini_section(OpcodeContext&); +void sf_npc_engine_level_up(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index e937b38a..cfe43989 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 "..\..\Drugs.h" #include "..\..\Explosions.h" #include "..\..\Inventory.h" #include "..\..\LoadGameHook.h" @@ -214,7 +215,7 @@ static DWORD getBlockingFunc(DWORD type) { return fo::funcoffs::obj_ai_blocking_at_; case BLOCKING_TYPE_SIGHT: return fo::funcoffs::obj_sight_blocking_at_; - //case 4: + //case 4: // return obj_scroll_blocking_at_; } @@ -494,10 +495,29 @@ void sf_get_object_ai_data(OpcodeContext& ctx) { value = arrayId; break; default: - ctx.printOpcodeError("get_object_ai_data() - invalid AI parameter."); + ctx.printOpcodeError("get_object_ai_data() - invalid value for AI argument."); } ctx.setReturn(value, DataType::INT); } +void sf_set_drugs_data(OpcodeContext& ctx) { + int type = ctx.arg(0).rawValue(); + int pid = ctx.arg(1).rawValue(); + int val = ctx.arg(2).rawValue(); + int result; + switch (type) { + case 0: + result = Drugs::SetDrugNumEffect(pid, val); + break; + case 1: + result = Drugs::SetDrugAddictTimeOff(pid, val); + break; + default: + ctx.printOpcodeError("set_drugs_data() - invalid value for type argument."); + return; + } + if (result) ctx.printOpcodeError("set_drugs_data() - drug PID not found in the configuration file."); +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Objects.h b/sfall/Modules/Scripting/Handlers/Objects.h index df882b22..74148ad4 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.h +++ b/sfall/Modules/Scripting/Handlers/Objects.h @@ -107,5 +107,7 @@ void sf_set_object_data(OpcodeContext&); void sf_get_object_ai_data(OpcodeContext&); +void sf_set_drugs_data(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index 071c9b86..89dd4609 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -19,6 +19,7 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\Perks.h" #include "..\..\ScriptExtender.h" +#include "..\OpcodeContext.h" #include "Perks.h" @@ -56,9 +57,9 @@ void __declspec(naked) op_set_perk_owed() { cmp eax, 250; jg end; mov byte ptr ds : [FO_VAR_free_perk], al - end : +end: popad - retn; + retn; } } @@ -76,38 +77,16 @@ void __declspec(naked) op_set_perk_freq() { call SetPerkFreq; end: popad - retn; + retn; } } -void __declspec(naked) op_get_perk_available() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_INT; - jnz fail; - cmp eax, PERK_count; - jge fail; - mov edx, eax; - mov eax, ds:[FO_VAR_obj_dude]; - call fo::funcoffs::perk_make_list_; - mov edx, eax; - jmp end; -fail: - xor edx, edx; -end: - mov eax, ecx; - call fo::funcoffs::interpretPushLong_; - mov edx, VAR_TYPE_INT; - mov eax, ecx; - call fo::funcoffs::interpretPushShort_; - popad; - retn; +void sf_get_perk_available(OpcodeContext& ctx) { + int result = 0, perkId = ctx.arg(0).rawValue(); + if (perkId >= 0 && perkId < 256) { // start fake id + result = fo::func::perk_can_add(fo::var::obj_dude, perkId); } + ctx.setReturn(result); } void __declspec(naked) op_set_perk_name() { @@ -461,77 +440,33 @@ end: void __declspec(naked) op_hide_real_perks() { __asm { - push ebx; push ecx; - push edx; call IgnoreDefaultPerks; - pop edx; - pop ecx; - pop ebx; + pop ecx; retn; } } void __declspec(naked) op_show_real_perks() { __asm { - push ebx; push ecx; - push edx; call RestoreDefaultPerks; - pop edx; - pop ecx; - pop ebx; + pop ecx; retn; } } void __declspec(naked) op_clear_selectable_perks() { __asm { - push ebx; push ecx; - push edx; call ClearSelectablePerks; - pop edx; - pop ecx; - pop ebx; + pop ecx; retn; } } -void __declspec(naked) op_has_fake_perk() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - mov edi, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push eax; - call HasFakePerk; -end: - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov eax, edi; - mov edx, VAR_TYPE_INT; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } +void sf_has_fake_perk(OpcodeContext& ctx) { + ctx.setReturn(HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); } void __declspec(naked) op_has_fake_trait() { @@ -645,13 +580,9 @@ end: void __declspec(naked) op_apply_heaveho_fix() { __asm { - push ebx; push ecx; - push edx; call ApplyHeaveHoFix; - pop edx; - pop ecx; - pop ebx; + pop ecx; retn; } } diff --git a/sfall/Modules/Scripting/Handlers/Perks.h b/sfall/Modules/Scripting/Handlers/Perks.h index ad4c2165..ef7a693a 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.h +++ b/sfall/Modules/Scripting/Handlers/Perks.h @@ -29,7 +29,7 @@ void __declspec() op_set_perk_owed(); void __declspec() op_set_perk_freq(); -void __declspec() op_get_perk_available(); +void sf_get_perk_available(OpcodeContext&); void __declspec() op_set_perk_name(); @@ -51,7 +51,7 @@ void __declspec() op_show_real_perks(); void __declspec() op_clear_selectable_perks(); -void __declspec() op_has_fake_perk(); +void sf_has_fake_perk(OpcodeContext&); void __declspec() op_has_fake_trait(); diff --git a/sfall/Modules/Scripting/Handlers/Utils.cpp b/sfall/Modules/Scripting/Handlers/Utils.cpp index 416897b2..7dd9c063 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.cpp +++ b/sfall/Modules/Scripting/Handlers/Utils.cpp @@ -92,29 +92,31 @@ void sf_typeof(OpcodeContext& ctx) { static int _stdcall StringSplit(const char* str, const char* split) { int id; - if (strlen(split) == 0) { - id = TempArray(strlen(str), 4); - for (DWORD i = 0; i < strlen(str); i++) { + size_t count, splitLen = strlen(split); + if (!splitLen) { + count = strlen(str); + id = TempArray(count, 0); + for (DWORD i = 0; i < count; i++) { arrays[id].val[i].set(&str[i], 1); } } else { - int count = 1; + count = 1; const char *ptr = str, *newptr; while (true) { newptr = strstr(ptr, split); if (!newptr) break; count++; - ptr = newptr + strlen(split); + ptr = newptr + splitLen; } id = TempArray(count, 0); ptr = str; count = 0; while (true) { newptr = strstr(ptr, split); - int len = newptr ? newptr - ptr : strlen(ptr); + int len = (newptr) ? newptr - ptr : strlen(ptr); arrays[id].val[count++].set(ptr, len); if (!newptr) break; - ptr = newptr + strlen(split); + ptr = newptr + splitLen; } } return id; diff --git a/sfall/Modules/Scripting/Handlers/Worldmap.cpp b/sfall/Modules/Scripting/Handlers/Worldmap.cpp index 5aa7fadb..c873710c 100644 --- a/sfall/Modules/Scripting/Handlers/Worldmap.cpp +++ b/sfall/Modules/Scripting/Handlers/Worldmap.cpp @@ -227,6 +227,36 @@ end: } } +void __declspec(naked) op_set_map_time_multi() { + __asm { + push ebx; + push ecx; + push edx; + mov ecx, eax; + call fo::funcoffs::interpretPopShort_; + mov edx, eax; + mov eax, ecx; + call fo::funcoffs::interpretPopLong_; + cmp dx, VAR_TYPE_FLOAT; + jz paramWasFloat; + cmp dx, VAR_TYPE_INT; + jnz fail; + push eax; + fild dword ptr [esp]; + fstp dword ptr [esp]; + jmp end; +paramWasFloat: + push eax; +end: + call SetMapMulti; +fail: + pop edx; + pop ecx; + pop ebx; + retn; + } +} + void sf_set_car_intface_art(OpcodeContext& ctx) { Worldmap::SetCarInterfaceArt(ctx.arg(0).asInt()); } diff --git a/sfall/Modules/Scripting/Handlers/Worldmap.h b/sfall/Modules/Scripting/Handlers/Worldmap.h index 2a1cce7f..1b6d4788 100644 --- a/sfall/Modules/Scripting/Handlers/Worldmap.h +++ b/sfall/Modules/Scripting/Handlers/Worldmap.h @@ -40,6 +40,8 @@ void __declspec() op_get_world_map_y_pos(); void __declspec() op_set_world_map_pos(); +void __declspec() op_set_map_time_multi(); + void sf_set_car_intface_art(OpcodeContext&); void sf_set_map_enter_position(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index ebf6beb5..ac17eb03 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -65,9 +65,13 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x163, "get_year", sf_get_year, 0, true}, {0x16c, "key_pressed", sf_key_pressed, 1, true, {ARG_INT}}, + {0x190, "get_perk_available", sf_get_perk_available, 1, true, {ARG_INT}}, {0x19d, "set_sfall_global", sf_set_sfall_global, 2, false, {ARG_INTSTR, ARG_NUMBER}}, {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}}, + + {0x1c1, "has_fake_perk", sf_has_fake_perk, 1, true, {ARG_INTSTR}}, {0x1dc, "show_iface_tag", sf_show_iface_tag, 1, false, {ARG_INT}}, {0x1dd, "hide_iface_tag", sf_hide_iface_tag, 1, false, {ARG_INT}}, @@ -130,6 +134,7 @@ 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}}, + {0x241, "get_npc_level", sf_get_npc_level, 1, true, {ARG_INTSTR}}, {0x24e, "substr", sf_substr, 3, true, {ARG_STRING, ARG_INT, ARG_INT}}, {0x24f, "strlen", sf_strlen, 1, true, {ARG_STRING}}, @@ -284,7 +289,6 @@ void InitNewOpcodes() { } opcodes[0x18e] = op_get_perk_owed; opcodes[0x18f] = op_set_perk_owed; - opcodes[0x190] = op_get_perk_available; opcodes[0x191] = op_get_critter_current_ap; opcodes[0x192] = op_set_critter_current_ap; opcodes[0x193] = op_active_hand; @@ -302,7 +306,6 @@ void InitNewOpcodes() { opcodes[0x1a2] = op_set_skill_max; opcodes[0x1a3] = op_eax_available; //opcodes[0x1a4] = op_set_eax_environment; - opcodes[0x1a5] = op_inc_npc_level; opcodes[0x1a6] = op_get_viewport_x; opcodes[0x1a7] = op_get_viewport_y; opcodes[0x1a8] = op_set_viewport_x; @@ -330,7 +333,6 @@ void InitNewOpcodes() { opcodes[0x1be] = op_set_perkbox_title; opcodes[0x1bf] = op_hide_real_perks; opcodes[0x1c0] = op_show_real_perks; - opcodes[0x1c1] = op_has_fake_perk; opcodes[0x1c2] = op_has_fake_trait; opcodes[0x1c3] = op_perk_add_mode; opcodes[0x1c4] = op_clear_selectable_perks; @@ -398,7 +400,6 @@ void InitNewOpcodes() { opcodes[0x23e] = op_force_aimed_shots; opcodes[0x23f] = op_disable_aimed_shots; opcodes[0x240] = op_mark_movie_played; - opcodes[0x241] = op_get_npc_level; opcodes[0x242] = op_set_critter_skill_points; opcodes[0x243] = op_get_critter_skill_points; opcodes[0x244] = op_set_available_skill_points; diff --git a/sfall/Modules/Skills.cpp b/sfall/Modules/Skills.cpp index d6a30d4d..d2257e1b 100644 --- a/sfall/Modules/Skills.cpp +++ b/sfall/Modules/Skills.cpp @@ -32,6 +32,8 @@ namespace sfall { +#define SKILL_MIN_LIMIT (-128) + struct SkillModifier { long id; int maximum; @@ -39,8 +41,8 @@ struct SkillModifier { SkillModifier() : id(0), maximum(300)/*, mod(0)*/ {} - SkillModifier(long _id, int max) { - id = _id; + SkillModifier(long id, int max) { + this->id = id; maximum = max; //mod = _mod; } @@ -61,6 +63,8 @@ static double* multipliers = nullptr; static std::vector pickpocketMods; static ChanceModifier basePickpocket; +static int skillNegPoints; // skill raw points (w/o limit) + static int __fastcall PickpocketMod(int base, fo::GameObject* critter) { for (DWORD i = 0; i < pickpocketMods.size(); i++) { if (critter->id == pickpocketMods[i].id) { @@ -92,15 +96,54 @@ static int __fastcall CheckSkillMax(fo::GameObject* critter, int base) { return min(base, baseSkillMax.maximum); } +static int __fastcall SkillNegative(fo::GameObject* critter, int base, int skill) { + int rawPoints = skillNegPoints; + if (rawPoints) { + if (rawPoints < SKILL_MIN_LIMIT) rawPoints = SKILL_MIN_LIMIT; + rawPoints *= fo::var::skill_data[skill].skillPointMulti; + if (fo::func::skill_is_tagged(skill)) rawPoints *= 2; + base += rawPoints; // add the negative skill points after calculating the skill level + if (base < 0) return max(-999, base); + } + return CheckSkillMax(critter, base); +} + static void __declspec(naked) skill_level_hack() { __asm { + push ebx; // skill mov edx, esi; // level skill (base) - call CheckSkillMax; // ecx - critter + call SkillNegative; // ecx - critter mov edx, 0x4AA64B; jmp edx; } } +static void __declspec(naked) skill_level_hook() { + __asm { + mov skillNegPoints, 0; // reset value + call fo::funcoffs::skill_points_; + test eax, eax; + jge notNeg; // skip if eax >= 0 + mov skillNegPoints, eax; // save the negative skill points + xor eax, eax; // set skill points to 0 +notNeg: + retn; + } +} + +static const DWORD skill_dec_point_limit_Ret = 0x4AAA91; +static void __declspec(naked) skill_dec_point_hack_limit() { + __asm { + cmp edi, SKILL_MIN_LIMIT; + jle skip; // if raw skill point <= -128 + add esp, 4; + jmp skill_dec_point_limit_Ret; +skip: + mov eax, -2; + retn; + } +} + static void __declspec(naked) skill_inc_point_force_hack() { __asm { push ecx; @@ -167,7 +210,10 @@ static void __declspec(naked) skill_inc_point_hack_cost() { next: mov edx, ebx; shl edx, 9; + test eax, eax; + jle skip; // if skill level <= 0 add edx, eax; +skip: movzx eax, skillCosts[edx]; // eax - cost of the skill jmp SkillIncCostRet; } @@ -175,7 +221,7 @@ next: static const DWORD SkillDecCostRet = 0x4AA98D; static void __declspec(naked) skill_dec_point_hack_cost() { - __asm { // eax - current skill level, ebx - current skill, ecx - num free skill points + __asm { // ecx - current skill level, ebx - current skill, esi - num free skill points mov edx, basedOnPoints; test edx, edx; jz next; @@ -186,7 +232,10 @@ static void __declspec(naked) skill_dec_point_hack_cost() { next: mov edx, ebx; shl edx, 9; + test ecx, ecx; + jle skip; // if skill level <= 0 add edx, ecx; +skip: movzx eax, skillCosts[edx]; // eax - cost of the skill jmp SkillDecCostRet; } @@ -196,7 +245,10 @@ static void __declspec(naked) skill_dec_point_hook_cost() { __asm { mov edx, ebx; shl edx, 9; + test eax, eax; + jle skip; // if skill level <= 0 add edx, eax; +skip: movzx eax, skillCosts[edx]; retn; } @@ -236,7 +288,7 @@ void _stdcall SetPickpocketMax(fo::GameObject* critter, DWORD maximum, DWORD mod pickpocketMods.push_back(ChanceModifier(id, maximum, mod)); } -void Skills_OnGameLoad() { +static void Skills_OnGameLoad() { pickpocketMods.clear(); basePickpocket.SetDefault(); @@ -249,6 +301,14 @@ void Skills::init() { MakeCall(0x4AA847, skill_inc_point_force_hack); MakeCall(0x4AA725, skill_inc_point_hack); + // fix for negative skill points + HookCall(0x4AA574, skill_level_hook); + // change the lower limit for negative skill points + MakeCall(0x4AAA84, skill_dec_point_hack_limit); + SafeWrite8(0x4AA91B, SKILL_MIN_LIMIT); + SafeWrite8(0x4AAA1A, SKILL_MIN_LIMIT); + SafeWrite32(0x4AAA23, SKILL_MIN_LIMIT); + MakeCall(0x4ABC62, skill_check_stealing_hack); // PickpocketMod SafeWrite8(0x4ABC67, 0x89); // mov [esp + 0x54], eax SafeWrite32(0x4ABC6B, 0x90909090); @@ -312,7 +372,9 @@ void Skills::init() { skills[i].base = GetPrivateProfileIntA("Skills", key, skills[i].base, file); sprintf(key, "SkillMulti%d", i); - skills[i].skillPointMulti = GetPrivateProfileIntA("Skills", key, skills[i].skillPointMulti, file); + int multi = GetPrivateProfileIntA("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); diff --git a/sfall/Modules/Stats.cpp b/sfall/Modules/Stats.cpp index 835edd37..5679f61b 100644 --- a/sfall/Modules/Stats.cpp +++ b/sfall/Modules/Stats.cpp @@ -258,7 +258,7 @@ void Stats::init() { StatFormulas[i * 2 + 1] = GetPrivateProfileInt(key, "min", StatFormulas[i * 2 + 1], statFile); for (int j = 0; j < fo::Stat::STAT_max_hit_points; j++) { sprintf(buf2, "shift%d", j); - StatShifts[i * 7 + j] = GetPrivateProfileInt(key, buf2, StatShifts[i * 7 + 0], statFile); + StatShifts[i * 7 + j] = GetPrivateProfileInt(key, buf2, StatShifts[i * 7 + j], statFile); sprintf(buf2, "multi%d", j); _gcvt(StatMulti[i * 7 + j], 16, buf3); GetPrivateProfileStringA(key, buf2, buf3, buf2, 256, statFile); diff --git a/sfall/Modules/Worldmap.h b/sfall/Modules/Worldmap.h index 5a28b3bd..d1dce2f8 100644 --- a/sfall/Modules/Worldmap.h +++ b/sfall/Modules/Worldmap.h @@ -44,4 +44,6 @@ public: static void SetAddedYears(DWORD years); }; +void _stdcall SetMapMulti(float d); + } diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 2055efa3..8041dd89 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -224,6 +224,7 @@ + @@ -312,6 +313,7 @@ + diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index 4d7e147b..dc0b0f6b 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -37,9 +37,6 @@ Modules - - Modules - Modules @@ -275,6 +272,12 @@ Modules + + Modules + + + Modules + @@ -296,9 +299,6 @@ Modules - - Modules - Modules @@ -510,6 +510,12 @@ Modules + + Modules + + + Modules + diff --git a/sfall/main.cpp b/sfall/main.cpp index 6c20d97c..4178e33b 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -36,6 +36,7 @@ #include "Modules\Credits.h" #include "Modules\Criticals.h" #include "Modules\DebugEditor.h" +#include "Modules\Drugs.h" #include "Modules\Elevators.h" #include "Modules\Explosions.h" #include "Modules\FileSystem.h" @@ -162,6 +163,7 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); // should be loaded before PartyControl manager.add(); manager.add(); manager.add(); diff --git a/sfall/stdafx.h b/sfall/stdafx.h index a1555b3a..faaaaf1c 100644 --- a/sfall/stdafx.h +++ b/sfall/stdafx.h @@ -6,5 +6,6 @@ #include #include #include +#include #include #include diff --git a/sfall/version.h b/sfall/version.h index d676a1f2..eec1920f 100644 --- a/sfall/version.h +++ b/sfall/version.h @@ -24,10 +24,10 @@ #define VERSION_MAJOR 4 #define VERSION_MINOR 1 -#define VERSION_BUILD 5 +#define VERSION_BUILD 6 #define VERSION_REV 0 -#define VERSION_STRING "4.1.5" +#define VERSION_STRING "4.1.6" -#define CHECK_VAL (4) +//#define CHECK_VAL (4)