diff --git a/artifacts/config_files/Translations.ini b/artifacts/config_files/Translations.ini index 756ec9258..6471fc09f 100644 --- a/artifacts/config_files/Translations.ini +++ b/artifacts/config_files/Translations.ini @@ -10,6 +10,7 @@ SaveSfallDataFail=ERROR saving extended savegame information! Check if other pro PartyLvlMsg=Lvl: PartyACMsg=AC: PartyAddictMsg=Addict +NPCPickupFail=%s cannot pick up the item. [AppearanceMod] RaceText=Race diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index 2dd9efee7..0fb211dd4 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -1,12 +1,12 @@ ;sfall configuration settings -;v4.1.7 +;v4.1.8 [Main] ;Change to 1 if you want to use command line args to tell sfall to use another ini file. UseCommandLine=0 ;Uncomment and point to a file to get alternate translations for some sfall messages -;TranslationsINI=./Translations.ini +;TranslationsINI=.\Translations.ini ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [ExtraPatches] @@ -30,7 +30,7 @@ AllowSoundForFloats=1 ;This does not effect the play_sfall_sound and stop_sfall_sound script functions AllowDShowSound=0 -;Set to 1 to override the music path used by default if not present in the cfg +;Set to 1 to override the music path used by default (i.e. data\sound\music\) if not present in the cfg ;Set to 2 to overwrite all occurances of the music path OverrideMusicDir=2 @@ -79,7 +79,7 @@ GraphicsHeight=0 GPUBlt=0 ;Set to 1 to allow using 32-bit textures for talking heads -;The texture files should be placed in art/heads// (w/o extension) +;The texture files should be placed in art\heads\\ (w/o extension) ;The files in the folder should be numbered according to the number of frames in the talking head FRM file (0.png, 1.png, etc.) ;See the text file in the modders pack for a detailed description ;Requires DX9 graphics mode and v2.0 pixel shader support (see GPUBlt option) @@ -218,8 +218,8 @@ UseFileSystemOverride=0 ;Modified: master_patches > critter_patches > patchXXX.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat DataLoadOrderPatch=0 -;Set to 1 to enable loading alternative dialog msg files from text/language/dialog_female/ for female PC -;Set to 2 to also load subtitle files from text/language/cuts_female/ for female PC +;Set to 1 to enable loading alternative dialog msg files from text\\dialog_female\ for female PC +;Set to 2 to also load subtitle files from text\\cuts_female\ for female PC FemaleDialogMsgs=0 ;To change the default and starting player models, uncomment the next four lines. @@ -256,7 +256,7 @@ Movie17=credits.mve ;StartDay=-1 ;To change the limit of the distance away from the player to which you're allowed to scroll the local maps, uncomment the next two lines -;Defaults are 0x1E0 in the x direction and 0x190 in the y direction. +;Defaults are 480 in the x direction and 400 in the y direction. ;Not compatible with the res patch! ;LocalMapXLimit=480 ;LocalMapYLimit=400 @@ -366,12 +366,8 @@ CorpseDeleteTime=6 ;Set to 1 (or some higher number if needed, the maximum is 127) to prevent 100% CPU use ProcessorIdle=-1 -;Set a number of how many protos per type can be loaded into memory at once (valid range: 512..4096) -;Set to -1 to let set_proto_data script function automatically increase the limit when needed -LoadProtoMaxLimit=-1 - ;Set to 1 if using the hero appearance mod -;You can add AppChCrt.frm and AppChEdt.frm files to art/intrface/ to set a custom background for the character screen +;You can add AppChCrt.frm and AppChEdt.frm files to art\intrface\ to set a custom background for the character screen EnableHeroAppearanceMod=0 ;Set to 1 to skip the 3 opening movies @@ -391,7 +387,7 @@ AIDrugUsePerfFix=0 ;Allows the use of tiles over 80*36 in size. sfall will just split and resave them at startup ;Set to 1 to check all tiles on started (slow) -;Set to 2 if you provide a XLtiles.lst file in art/tiles/ containing a list of the tile ids that need checking +;Set to 2 if you provide a XLtiles.lst file in art\tiles\ containing a list of the tile ids that need checking AllowLargeTiles=0 ;Set to 1 to boost the maximum number of tile FRMs from 4096 to 16383 @@ -617,9 +613,6 @@ ReloadReserve=-1 ;Set to 1 to change the counter in the 'Move Items' window to start at the maximum number of items ItemCounterDefaultMax=0 -;Allows 9 options (lines of text) to be displayed correctly in a dialog window -DialogOptions9Lines=1 - ;Set to 1 to leave the music playing in dialogue with talking heads EnableMusicInDialogue=0 @@ -630,7 +623,7 @@ DontTurnOffSneakIfYouRun=0 ;Set to 0 to disable switching. (Default is 3) UseWalkDistance=3 -;Set to 1 to fix the bug that unable to sell used geiger counters or stealth boys +;Set to 1 to fix the bug of being unable to sell used geiger counters or stealth boys CanSellUsedGeiger=1 ;Set to 1 to fix the issue with being able to charge the car by using cells on other scenary/critters @@ -693,7 +686,7 @@ CreditsAtBottom=0 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Scripts] ;Comma-separated list of masked paths to load global scripts from -;Only use single slash \ as directory separator +;Only use single backslash \ as the directory separator ;Paths outside of scripts folder are supported GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int @@ -733,7 +726,7 @@ SkipSizeCheck=0 ;Does not require sfall debugging mode ;ExtraCRC=0x00000000,0x00000000 -;Set to 1 to stop Fallout from deleting non readonly protos at startup +;Set to 1 to stop Fallout from deleting non read-only protos at startup ;Has pretty nasty side effects when saving/reloading, so don't use for regular gameplay DontDeleteProtos=0 @@ -749,6 +742,10 @@ InjectAllGameHooks=0 ;Set to 1 to hide error messages in debug output when a null value is passed to the function as an object HideObjIsNullMsg=0 +;Set to 1 to force critters to display combat float messages +;Requires AllowSoundForFloats to be enabled +Test_ForceFloats=0 + ;These options control what output is saved in the debug log (sfall-log.txt) ;Prints messages duing sfall initialization diff --git a/artifacts/example_mods/AmmoMod/AmmoGlovz.ini b/artifacts/example_mods/AmmoMod/AmmoGlovz.ini new file mode 100644 index 000000000..5441e2f03 --- /dev/null +++ b/artifacts/example_mods/AmmoMod/AmmoGlovz.ini @@ -0,0 +1,201 @@ +;Glovz's Damage Fix + +; PID_EXPLOSIVE_ROCKET +[1] +pid=14 +ac_adjust=-10 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_10MM_JHP +[2] +pid=29 +ac_adjust=0 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_10MM_AP +[3] +pid=30 +ac_adjust=-5 +dr_adjust=0 +dam_mult=1 +dam_div=2 + +; PID_44_MAGNUM_JHP +[4] +pid=31 +ac_adjust=0 +dr_adjust=-10 +dam_mult=2 +dam_div=1 + +; PID_FLAMETHROWER_FUEL +[5] +pid=32 +ac_adjust=-20 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_14MM_AP +[6] +pid=33 +ac_adjust=-5 +dr_adjust=-30 +dam_mult=1 +dam_div=2 + +; PID_223_FMJ +[7] +pid=34 +ac_adjust=-15 +dr_adjust=-15 +dam_mult=2 +dam_div=2 + +; PID_5MM_JHP +[8] +pid=35 +ac_adjust=0 +dr_adjust=-15 +dam_mult=2 +dam_div=1 + +; PID_5MM_AP +[9] +pid=36 +ac_adjust=-5 +dr_adjust=-15 +dam_mult=1 +dam_div=2 + +; PID_ROCKET_AP +[10] +pid=37 +ac_adjust=-10 +dr_adjust=0 +dam_mult=1 +dam_div=2 + +; PID_SMALL_ENERGY_CELL +[11] +pid=38 +ac_adjust=-20 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_MICRO_FUSION_CELL +[12] +pid=39 +ac_adjust=-20 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_SHOTGUN_SHELLS +[13] +pid=95 +ac_adjust=-15 +dr_adjust=0 +dam_mult=3 +dam_div=1 + +; PID_44_FMJ_MAGNUM +[14] +pid=111 +ac_adjust=-5 +dr_adjust=-10 +dam_mult=2 +dam_div=2 + +; PID_9MM_BALL +[15] +pid=121 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_BBS +[16] +pid=163 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_ROBO_ROCKET_AMMO +[17] +pid=274 +ac_adjust=-10 +dr_adjust=-10 +dam_mult=1 +dam_div=1 + +; PID_45_CALIBER_AMMO +[18] +pid=357 +ac_adjust=0 +dr_adjust=-5 +dam_mult=2 +dam_div=1 + +; PID_2MM_EC_AMMO +[19] +pid=358 +ac_adjust=-25 +dr_adjust=-20 +dam_mult=1 +dam_div=3 + +; PID_4_7MM_CASELESS +[20] +pid=359 +ac_adjust=-5 +dr_adjust=-25 +dam_mult=1 +dam_div=2 + +; PID_9MM_AMMO +[21] +pid=360 +ac_adjust=0 +dr_adjust=0 +dam_mult=2 +dam_div=1 + +; PID_HN_NEEDLER_CARTRIDGE +[22] +pid=361 +ac_adjust=-20 +dr_adjust=-5 +dam_mult=1 +dam_div=1 + +; PID_HN_AP_NEEDLER_CARTRIDGE +[23] +pid=362 +ac_adjust=-20 +dr_adjust=-5 +dam_mult=1 +dam_div=2 + +; PID_7_62MM_AMMO +[24] +pid=363 +ac_adjust=-10 +dr_adjust=-20 +dam_mult=2 +dam_div=2 + +; PID_FLAMETHROWER_FUEL_MK_II +[25] +pid=382 +ac_adjust=-20 +dr_adjust=-15 +dam_mult=1 +dam_div=1 diff --git a/artifacts/example_mods/AmmoMod/AmmoYAAM.ini b/artifacts/example_mods/AmmoMod/AmmoYAAM.ini new file mode 100644 index 000000000..07cdbb781 --- /dev/null +++ b/artifacts/example_mods/AmmoMod/AmmoYAAM.ini @@ -0,0 +1,201 @@ +;YAAM + +; PID_EXPLOSIVE_ROCKET +[1] +pid=14 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_10MM_JHP +[2] +pid=29 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_10MM_AP +[3] +pid=30 +ac_adjust=0 +dr_adjust=4 +dam_mult=1 +dam_div=1 + +; PID_44_MAGNUM_JHP +[4] +pid=31 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_FLAMETHROWER_FUEL +[5] +pid=32 +ac_adjust=-20 +dr_adjust=0 +dam_mult=3 +dam_div=4 + +; PID_14MM_AP +[6] +pid=33 +ac_adjust=0 +dr_adjust=8 +dam_mult=1 +dam_div=1 + +; PID_223_FMJ +[7] +pid=34 +ac_adjust=-20 +dr_adjust=5 +dam_mult=1 +dam_div=1 + +; PID_5MM_JHP +[8] +pid=35 +ac_adjust=0 +dr_adjust=0 +dam_mult=3 +dam_div=2 + +; PID_5MM_AP +[9] +pid=36 +ac_adjust=0 +dr_adjust=4 +dam_mult=1 +dam_div=1 + +; PID_ROCKET_AP +[10] +pid=37 +ac_adjust=0 +dr_adjust=20 +dam_mult=1 +dam_div=1 + +; PID_SMALL_ENERGY_CELL +[11] +pid=38 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_MICRO_FUSION_CELL +[12] +pid=39 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_SHOTGUN_SHELLS +[13] +pid=95 +ac_adjust=-10 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_44_FMJ_MAGNUM +[14] +pid=111 +ac_adjust=0 +dr_adjust=5 +dam_mult=1 +dam_div=1 + +; PID_9MM_BALL +[15] +pid=121 +ac_adjust=0 +dr_adjust=2 +dam_mult=3 +dam_div=2 + +; PID_BBS +[16] +pid=163 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_ROBO_ROCKET_AMMO +[17] +pid=274 +ac_adjust=0 +dr_adjust=10 +dam_mult=1 +dam_div=1 + +; PID_45_CALIBER_AMMO +[18] +pid=357 +ac_adjust=0 +dr_adjust=0 +dam_mult=1 +dam_div=1 + +; PID_2MM_EC_AMMO +[19] +pid=358 +ac_adjust=-30 +dr_adjust=9 +dam_mult=3 +dam_div=2 + +; PID_4_7MM_CASELESS +[20] +pid=359 +ac_adjust=-5 +dr_adjust=7 +dam_mult=3 +dam_div=2 + +; PID_9MM_AMMO +[21] +pid=360 +ac_adjust=0 +dr_adjust=2 +dam_mult=1 +dam_div=1 + +; PID_HN_NEEDLER_CARTRIDGE +[22] +pid=361 +ac_adjust=-10 +dr_adjust=2 +dam_mult=1 +dam_div=1 + +; PID_HN_AP_NEEDLER_CARTRIDGE +[23] +pid=362 +ac_adjust=-10 +dr_adjust=5 +dam_mult=2 +dam_div=1 + +; PID_7_62MM_AMMO +[24] +pid=363 +ac_adjust=-5 +dr_adjust=2 +dam_mult=1 +dam_div=1 + +; PID_FLAMETHROWER_FUEL_MK_II +[25] +pid=382 +ac_adjust=-20 +dr_adjust=0 +dam_mult=1 +dam_div=1 diff --git a/artifacts/example_mods/AmmoMod/gl_ammomod.int b/artifacts/example_mods/AmmoMod/gl_ammomod.int new file mode 100644 index 000000000..2730de62e Binary files /dev/null and b/artifacts/example_mods/AmmoMod/gl_ammomod.int differ diff --git a/artifacts/example_mods/AmmoMod/gl_ammomod.ssl b/artifacts/example_mods/AmmoMod/gl_ammomod.ssl new file mode 100644 index 000000000..b57428688 --- /dev/null +++ b/artifacts/example_mods/AmmoMod/gl_ammomod.ssl @@ -0,0 +1,63 @@ +/* + +Ammo INI Loader mod for Fallout 2 by NovaRain +--------------------------------------------- + +- modifies ammo protos with data from an INI file: + * AmmoGlovz.ini if DamageFormula=1 or 2 in ddraw.ini + * AmmoYAAM.ini if DamageFormula=5 in ddraw.ini + * AmmoMod.ini if not using any bulit-in damage formula + +Requires sfall 3.5 or higher + +*/ + +#include "..\headers\define_extra.h" + +procedure set_ammo_mod; +procedure start; +procedure map_enter_p_proc; + +variable ammoIni; +variable enabled; + +procedure set_ammo_mod begin + variable pid, dmg_mod, i := 1; + + while (i > 0) do begin + pid := get_ini_setting(ammoIni + "|" + i + "|pid"); + if (pid < 1) then break; + set_proto_data(pid, PROTO_AM_AC_MOD, get_ini_setting(ammoIni + "|" + i + "|ac_adjust")); + set_proto_data(pid, PROTO_AM_DR_MOD, get_ini_setting(ammoIni + "|" + i + "|dr_adjust")); + + // dam_mult and dam_div must be positive integers + dmg_mod := get_ini_setting(ammoIni + "|" + i + "|dam_mult"); + if (dmg_mod < 1) then dmg_mod := 1; + set_proto_data(pid, PROTO_AM_DMG_MULT, dmg_mod); + dmg_mod := get_ini_setting(ammoIni + "|" + i + "|dam_div"); + if (dmg_mod < 1) then dmg_mod := 1; + set_proto_data(pid, PROTO_AM_DMG_DIV, dmg_mod); + + i++; + end + //debug_msg("AmmoMod: " + ammoIni + " - set " + (i - 1) + " ammo protos."); +end + +procedure start begin + if game_loaded then begin + ammoIni := get_ini_setting("ddraw.ini|Misc|DamageFormula"); + if (ammoIni == 1 or ammoIni == 2) then + ammoIni := "AmmoGlovz.ini"; + else if (ammoIni == 5) then + ammoIni := "AmmoYAAM.ini"; + else + ammoIni := "AmmoMod.ini"; + + enabled := get_ini_setting(ammoIni + "|1|pid"); + if (enabled > 0) then call set_ammo_mod; + end +end + +procedure map_enter_p_proc begin + if (enabled > 0) then call set_ammo_mod; +end diff --git a/artifacts/example_mods/AmmoMod/readme.txt b/artifacts/example_mods/AmmoMod/readme.txt new file mode 100644 index 000000000..cdae1926a --- /dev/null +++ b/artifacts/example_mods/AmmoMod/readme.txt @@ -0,0 +1,12 @@ +Ammo INI Loader mod for Fallout 2 by NovaRain +--------------------------------------------- + +- modifies ammo protos with data from an INI file: + * AmmoGlovz.ini if DamageFormula=1 or 2 in ddraw.ini + * AmmoYAAM.ini if DamageFormula=5 in ddraw.ini + * AmmoMod.ini if not using any bulit-in damage formula + + +Requires sfall 3.5 or higher. + +To use, copy gl_ammomod.int to your scripts folder, and copy the INI files to the same directory as sfall. diff --git a/artifacts/scripting/headers/define_extra.h b/artifacts/scripting/headers/define_extra.h index 346b701b0..92a095f20 100644 --- a/artifacts/scripting/headers/define_extra.h +++ b/artifacts/scripting/headers/define_extra.h @@ -10,6 +10,12 @@ #define OBJ_TYPE_MISC (5) #define OBJ_TYPE_SPATIAL (6) +#define ART_TYPE_INTERFACE (6) +#define ART_TYPE_INVENT (7) +#define ART_TYPE_HEADS (8) +#define ART_TYPE_BACKGRND (9) +#define ART_TYPE_SKILLDEX (10) + #define WEAPON_TYPE_NONE (0) #define WEAPON_TYPE_UNARMED (1) #define WEAPON_TYPE_MELEE (2) diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index a3789051a..65c085e8a 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -238,6 +238,11 @@ #define party_member_list_critters party_member_list(0) #define party_member_list_all party_member_list(1) +// fake perks/traits add mode flags +#define ADD_PERK_MODE_TRAIT (1) // add to the player's traits +#define ADD_PERK_MODE_PERK (2) // add to the player's perks +#define ADD_PERK_MODE_REMOVE (4) // remove from the list of selectable perks + // sfall_funcX macros #define add_iface_tag sfall_func0("add_iface_tag") #define art_cache_clear sfall_func0("art_cache_clear") @@ -249,6 +254,8 @@ #define dialog_message(text) sfall_func1("dialog_message", text) #define dialog_obj sfall_func0("dialog_obj") #define display_stats sfall_func0("display_stats") +#define draw_image(frmFile, frame, x, y, noTransparent) sfall_func5("draw_image", frmFile, frame, x, y, noTransparent) +#define draw_image_scaled(frmFile, frame, x, y, w, h) sfall_func6("draw_image_scaled", frmFile, frame, x, y, w, h) #define exec_map_update_scripts sfall_func0("exec_map_update_scripts") #define floor2(value) sfall_func1("floor2", value) #define get_can_rest_on_map(map, elev) sfall_func2("get_can_rest_on_map", map, elev) @@ -262,6 +269,8 @@ #define get_object_data(obj, offset) sfall_func2("get_object_data", obj, offset) #define get_outline(obj) sfall_func1("get_outline", obj) #define get_string_pointer(text) sfall_func1("get_string_pointer", text) +#define has_fake_perk_npc(npc, perk) sfall_func2("has_fake_perk_npc", npc, perk) +#define has_fake_trait_npc(npc, trait) sfall_func2("has_fake_trait_npc", npc, trait) #define intface_hide sfall_func0("intface_hide") #define intface_is_hidden sfall_func0("intface_is_hidden") #define intface_redraw sfall_func0("intface_redraw") @@ -280,6 +289,8 @@ #define set_cursor_mode(mode) sfall_func1("set_cursor_mode", mode) #define set_drugs_data(type, pid, value) sfall_func3("set_drugs_data", type, pid, value) #define set_dude_obj(critter) sfall_func1("set_dude_obj", critter) +#define set_fake_perk_npc(npc, perk, level, image, desc) sfall_func5("set_fake_perk_npc", npc, perk, level, image, desc) +#define set_fake_trait_npc(npc, trait, active, image, desc) sfall_func5("set_fake_trait_npc", npc, trait, active, image, desc) #define set_flags(obj, flags) sfall_func2("set_flags", obj, flags) #define set_iface_tag_text(tag, text, color) sfall_func3("set_iface_tag_text", tag, text, color) #define set_ini_setting(setting, value) sfall_func2("set_ini_setting", setting, value) @@ -288,6 +299,7 @@ #define set_outline(obj, color) sfall_func2("set_outline", obj, color) #define set_rest_heal_time(time) sfall_func1("set_rest_heal_time", time) #define set_rest_mode(mode) sfall_func1("set_rest_mode", mode) +#define set_selectable_perk_npc(npc, perk, active, image, desc) sfall_func5("set_selectable_perk_npc", npc, perk, active, image, desc) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) #define unset_unique_id(obj) sfall_func2("set_unique_id", obj, -1) #define set_unjam_locks_time(time) sfall_func1("set_unjam_locks_time", time) diff --git a/artifacts/scripting/hookscripts.txt b/artifacts/scripting/hookscripts.txt index 923f03268..6cf7a1760 100644 --- a/artifacts/scripting/hookscripts.txt +++ b/artifacts/scripting/hookscripts.txt @@ -421,7 +421,7 @@ What you can do: - add AP costs for all inventory movement including reloading - apply or remove some special scripted effects depending on PC's armor -int arg1 - Target slot (0 - main backpack, 1 - left hand, 2 - right hand, 3 - armor slot, 4 - weapon, when reloading it by dropping ammo, 5 - container, like bag/backpack, 6 - dropping on the ground, 7 - picking up item) +int arg1 - Target slot (0 - main backpack, 1 - left hand, 2 - right hand, 3 - armor slot, 4 - weapon, when reloading it by dropping ammo, 5 - container, like bag/backpack, 6 - dropping on the ground, 7 - picking up item, 8 - dropping item on the character portrait) Item arg2 - Item being moved Item arg3 - Item being replaced, weapon being reloaded, or container being filled (can be 0) diff --git a/artifacts/scripting/sfall function notes.txt b/artifacts/scripting/sfall function notes.txt index 49b7dd204..52f3eb35a 100644 --- a/artifacts/scripting/sfall function notes.txt +++ b/artifacts/scripting/sfall function notes.txt @@ -32,7 +32,7 @@ set_shader_mode tells sfall when to use a shader. The parameter is a set of 32 f force_graphics_refresh forces the screen to redraw at times when it normally wouldn't. If you're using animated shader, turning this option on is recommended. -The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', which supposedly return the players x and y positions on the world map. get_world_map_x/y_pos are included here anyway, because I was unable to get those original functions to work, or even to find any evidence that they existed in game. +The mapper manual lists the functions 'world_map_x_pos' and 'world_map_y_pos', which supposedly return the player's x and y positions on the world map. get_world_map_x/y_pos are included here anyway, because I was unable to get those original functions to work, or even to find any evidence that they existed in game. set_pipboy_available will only accept 0 or 1 as an argument. Using any other value will cause the function to have no effect. Use 0 to disable the pipboy, and 1 to enable it. @@ -42,13 +42,13 @@ The 'type' value in the weapon knockback functions can be 0 or 1. If 0, the valu The get/set_sfall_global functions require an 8 characters long case sensitive string for the variable name. The variables behave the same as normal Fallout globals, except that they don't have to be declared beforehand in vault13.gam. Trying to get a variable which hasn't been set will always return 0. These functions are intended for use when a patch to a mod requires the addition of a new global variable, a case which would otherwise require the player to start a new game. -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_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 player's 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 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 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. +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 player's traits, if bit 2 is set it is added to the player's 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. show_iface_tag, hide_iface_tag and is_iface_tag_active relate to the boxes that appear above the interface such as SNEAK and LEVEL. You can use 3 for LEVEL and 4 for ADDICT, or the range from 5 to (4 + the value of BoxBarCount in ddraw.ini) for custom boxes. Remember to add your messages to intrface.msg and set up the font colours in ddraw.ini if you're going to use custom boxes. Starting from sfall 4.1, is_iface_tag_active can also be used to check 0 for SNEAK, 1 for POISONED, and 2 for RADIATED. @@ -317,7 +317,7 @@ Some utility/math functions are available: - see "hookscripts.txt" for more details > string message_str_game(int fileId, int messageId) -- works exactly the same as message_str, except you get messages from files in "text/english/game" folder +- works exactly the same as message_str, except you get messages from files in "text\\game\" directory - use GAME_MSG_* defines or mstr_* macros from sfall.h to use specific msg file - Additional game msg files added by ExtraGameMsgFileList setting will have consecutive fileIds assigned beginning from 0x2000. (e.g. if you set ExtraGameMsgFileList=foo,bar in ddraw.ini, foo.msg will be associated with 0x2000 and bar.msg with 0x2001.) - if a file has a specific number assigned in ExtraGameMsgFileList, its fileId will be (0x2000 + assigned number). (e.g. with ExtraGameMsgFileList=foo,bar:2,foobar in ddraw.ini, bar.msg will be associated with 0x2002 and foobar.msg with 0x2003.) @@ -587,6 +587,25 @@ Some utility/math functions are available: - there is also a unique ID number range for the player and party members from 18000 to 83535 - to assign a new ID number generated by the engine to the object (i.e. unassign a unique ID), call the function with two arguments and pass -1 for the flag argument +> void sfall_func5("draw_image", string/int pathFile/artId, int frame, int x, int y, bool noTransparent) +> void sfall_func6("draw_image_scaled", string/int pathFile/artId, int frame, int x, int y, int width, int height) +- displays the specified FRM image in the active window created by vanilla CreateWin or sfall's create_win script function +- pathFile/artId: path to the FRM file (e.g. "art\\inven\\5mmap.frm"), or its FRM ID number (e.g. 117440550, see specification of the FID format) +optional arguments: +- frame: frame number, the first frame starts from zero +- x/y: offset relative to the top-left corner of the window +- width/height: image size, used to scale the image when displaying it. Pass -1 to either width or height to keep the aspect ratio when scaling +- noTransparent: pass true to display an image without transparent background +- NOTE: to omit optional arguments starting from the right, call the functions with different sfall_funcX (e.g. sfall_func4("draw_image", pathFile, frame, x, y)) +- if draw_image_scaled is called without x/y/width/height arguments, the image will be scaled to fit the window without transparent background + +> void sfall_func5("set_fake_perk_npc", object npc, string namePerk, int level, int image, string desc) +> void sfall_func5("set_fake_trait_npc", object npc, string nameTrait, int active, int image, string desc) +> void sfall_func5("set_selectable_perk_npc", object npc, string namePerk, int active, int image, string desc) +> int sfall_func2("has_fake_perk_npc", object npc, string namePerk) +> int sfall_func2("has_fake_trait_npc", object npc, string nameTrait) +- these functions are similar to has_fake_*/set_fake_*/set_selectable_perk functions, but apply to the specified party member NPC (including dude_obj) + ------------------------ ------ MORE INFO ------- ------------------------ diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index e23d972f8..72d56ebd7 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -62,6 +62,14 @@ Proto* GetProto(long pid) { return nullptr; } +bool CritterCopyProto(long pid, long* &proto_dst) { + fo::Proto* protoPtr; + if (fo::func::proto_ptr(pid, &protoPtr) == -1) return false; + /*if (!proto_dst)*/ proto_dst = new long[104]; + memcpy(proto_dst, protoPtr, 416); + return true; +} + void SkillGetTags(long* result, long num) { if (num > 4) { num = 4; @@ -76,11 +84,11 @@ void SkillSetTags(long* tags, long num) { fo::func::skill_set_tags(tags, num); } -int __fastcall GetItemType(GameObject* item) { +long __fastcall GetItemType(GameObject* item) { return fo::func::item_get_type(item); } -_declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot) { +__declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot) { GameObject* itemPtr = nullptr; switch (slot) { case fo::INVEN_TYPE_LEFT_HAND: @@ -129,16 +137,21 @@ void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set) { } } -bool IsPartyMember(fo::GameObject* critter) { - if (critter->id < 18000) return false; +// Returns the number of party members in the existing table (begins from 1) +long IsPartyMemberByPid(long pid) { size_t patryCount = fo::var::partyMemberMaxCount; if (patryCount) { DWORD* memberPids = fo::var::partyMemberPidList; // pids from party.txt for (size_t i = 0; i < patryCount; i++) { - if (memberPids[i] == critter->protoId) return true;; + if (memberPids[i] == pid) return i + 1; } } - return false; + return 0; +} + +bool IsPartyMember(fo::GameObject* critter) { + if (critter->id < PLAYER_ID) return false; + return (IsPartyMemberByPid(critter->protoId) > 0); } //--------------------------------------------------------- diff --git a/sfall/FalloutEngine/EngineUtils.h b/sfall/FalloutEngine/EngineUtils.h index 1386b344f..873515a5a 100644 --- a/sfall/FalloutEngine/EngineUtils.h +++ b/sfall/FalloutEngine/EngineUtils.h @@ -44,15 +44,17 @@ const char* _stdcall MessageSearch(const MessageList* fileAddr, long messageId); // returns pointer to prototype by PID, or nullptr on failure Proto* GetProto(long pid); +bool CritterCopyProto(long pid, long* &proto_dst); + // wrapper for skill_get_tags with bounds checking void SkillGetTags(long* result, long num); // wrapper for skill_set_tags with bounds checking void SkillSetTags(long* tags, long num); -int __fastcall GetItemType(GameObject* item); +long __fastcall GetItemType(GameObject* item); -_declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot); +__declspec(noinline) GameObject* GetItemPtrSlot(GameObject* critter, InvenType slot); long& GetActiveItemMode(); @@ -64,6 +66,9 @@ long CheckAddictByPid(fo::GameObject* critter, long pid); void ToggleNpcFlag(fo::GameObject* npc, long flag, bool set); +// Returns the number of party members in the existing table (begins from 1) +long IsPartyMemberByPid(long pid); + bool IsPartyMember(fo::GameObject* critter); // Print text to surface diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index 905c1a161..74c9564ea 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -641,7 +641,9 @@ enum BodyType : long Robotic = 2 }; -#define OBJFLAG_CAN_WEAR_ITEMS (0xf000000) +#define PLAYER_ID (18000) + +#define OBJFLAG_CAN_WEAR_ITEMS (0xF000000) #define OBJFLAG_HELD_IN_RIGHT (0x10000) #define OBJFLAG_HELD_IN_LEFT (0x20000) @@ -660,6 +662,12 @@ enum TicksTime : unsigned long ONE_GAME_YEAR = 315360000 }; +enum ActiveSlot : unsigned long +{ + Left = 0, + Right = 1 +}; + enum RollResult { ROLL_CRITICAL_FAILURE = 0x0, @@ -693,8 +701,8 @@ namespace Fields { scriptIndex = 0x80, }; - enum CritterObj : long - { + enum CritterObj : long + { reaction = 0x38, combatState = 0x3C, movePoints = 0x40, diff --git a/sfall/FalloutEngine/Fallout2.h b/sfall/FalloutEngine/Fallout2.h index 0c1c5a4d3..e860bbb95 100644 --- a/sfall/FalloutEngine/Fallout2.h +++ b/sfall/FalloutEngine/Fallout2.h @@ -69,7 +69,7 @@ Returns the value to the script eax register must contain the script_ptr edx register must contain the returned value - rscript - name register for temporary save script_ptr + rscript - register name for temporarily saving script_ptr */ #define _RET_VAL_INT(rscript) __asm { \ __asm mov rscript, eax \ diff --git a/sfall/FalloutEngine/FunctionOffsets_def.h b/sfall/FalloutEngine/FunctionOffsets_def.h index 02d7d2e04..dc42ab6d7 100644 --- a/sfall/FalloutEngine/FunctionOffsets_def.h +++ b/sfall/FalloutEngine/FunctionOffsets_def.h @@ -156,6 +156,7 @@ FUNC(LoadLibraryA_0, 0x4FE1A8) FUNC(LoadObjDudeCid_, 0x480710) FUNC(LoadSlot_, 0x47DC68) FUNC(LoadTumbSlot_, 0x47EC48) +FUNC(MapDirErase_, 0x480040) FUNC(MVE_MemAlloc_, 0x4F4D40) FUNC(MVE_MemFree_, 0x4F48C0) FUNC(MVE_MemInit_, 0x4F4890) diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index d891082bd..7c35070e8 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -165,7 +165,7 @@ long __stdcall db_init(const char* path_dat, const char* path_patches) { } // Check fallout paths for file -long __stdcall CheckFile(char *fileName, DWORD *sizeOut) { +long __stdcall db_file_exist(const char *fileName, DWORD *sizeOut) { WRAP_WATCOM_CALL2(db_dir_entry_, fileName, sizeOut) } @@ -200,11 +200,6 @@ void __stdcall gsound_play_sfx_file(const char* name) { WRAP_WATCOM_CALL1(gsound_play_sfx_file_, name) } -// redraws the main game interface windows (useful after changing some data like active hand, etc.) -void intface_redraw() { - WRAP_WATCOM_CALL0(intface_redraw_) -} - long __stdcall interpret(Program* program, long arg2) { WRAP_WATCOM_CALL2(interpret_, program, arg2) } @@ -354,7 +349,7 @@ GameObject* __stdcall scr_find_obj_from_program(Program* program) { WRAP_WATCOM_CALL1(scr_find_obj_from_program_, program) } -// Saves pointer to script object into scriptPtr using scriptID. +// Saves pointer to script object into scriptPtr using scriptID. // Returns 0 on success, -1 on failure. long __stdcall scr_ptr(long scriptId, ScriptInstance** scriptPtr) { WRAP_WATCOM_CALL2(scr_ptr_, scriptId, scriptPtr) @@ -399,8 +394,8 @@ void __stdcall DialogOut(const char* text) { push eax; // DisplayMsg mov al, byte ptr ds:[0x6AB718]; push eax; // ColorIndex - push 0x74; // y - mov ecx, 0xC0; // x + push 116; // y + mov ecx, 192; // x mov eax, text; // DisplayText xor ebx, ebx; // ? xor edx, edx; // ? @@ -422,6 +417,44 @@ void __fastcall DrawWinLine(int winRef, DWORD startXPos, DWORD endXPos, DWORD st } } +// draws an image to the buffer without scaling and with transparency display toggle +void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long noTrans) { + __asm { + push height; + push edx; // from_width + push y; + mov eax, data; // from + mov ebx, fo::funcoffs::windowDisplayTransBuf_; + cmp noTrans, 0; + cmovnz ebx, fo::funcoffs::windowDisplayBuf_; + call ebx; // *data, from_width, unused, X, Y, width, height + } +} + +// draws an image in the window and scales it to fit the window +void __fastcall displayInWindow(long w_here, long width, long height, void* data) { + __asm { + mov ebx, height; + mov eax, data; + call fo::funcoffs::displayInWindow_; // *data, width, height, where + } +} + +void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_height, long xy_shift, long w_width, void* data) { + __asm { + push w_width; + push s_height; + push s_width; + mov ebx, edx; // i_height + mov edx, ecx; // i_width + call fo::funcoffs::windowGetBuffer_; + add eax, xy_shift; + push eax; // to_buff + mov eax, data; + call fo::funcoffs::trans_cscale_; // *from_buff, i_width, i_height, i_width2, to_buff, width, height, to_width + } +} + #define WRAP_WATCOM_FUNC0(retType, name) \ retType __stdcall name() { \ diff --git a/sfall/FalloutEngine/Functions.h b/sfall/FalloutEngine/Functions.h index af8997365..869eaa784 100644 --- a/sfall/FalloutEngine/Functions.h +++ b/sfall/FalloutEngine/Functions.h @@ -20,8 +20,8 @@ #include "Structs.h" -// -// WRAPPERS for FO engine functions. +// +// WRAPPERS for FO engine functions. // Use those as you would if there were source code for the engine... // namespace fo @@ -78,6 +78,8 @@ long __stdcall db_get_file_list(const char* searchMask, char* * *fileList); long __stdcall db_init(const char* path_dat, const char* path_patches); +long __stdcall db_file_exist(const char *fileName, DWORD *sizeOut); + // prints message to debug.log file void __declspec() debug_printf(const char* fmt, ...); @@ -96,12 +98,9 @@ long __stdcall item_get_type(GameObject* item); // searches for message ID in given message file and places result in @result const char* _stdcall getmsg(const MessageList* fileAddr, MessageNode* result, long messageId); -// plays SFX sound with given name +// plays SFX sound with given name void __stdcall gsound_play_sfx_file(const char* name); -// redraws the main game interface windows (useful after changing some data like active hand, etc.) -void intface_redraw(); - long __stdcall interpret(Program* program, long arg2); // finds procedure ID for given script program pointer and procedure name @@ -183,7 +182,7 @@ ScriptInstance* __stdcall scr_find_next_at(); GameObject* __stdcall scr_find_obj_from_program(Program* program); -// Saves pointer to script object into scriptPtr using scriptID. +// Saves pointer to script object into scriptPtr using scriptID. // Returns 0 on success, -1 on failure. long __stdcall scr_ptr(long scriptId, ScriptInstance** scriptPtr); @@ -199,6 +198,14 @@ long __stdcall win_register_button(DWORD winRef, long xPos, long yPos, long widt void __stdcall DialogOut(const char* text); +// draws an image to the buffer without scaling and with transparency display toggle +void __fastcall windowDisplayBuf(long x, long width, long y, long height, void* data, long noTrans); + +// draws an image in the window and scales it to fit the window +void __fastcall displayInWindow(long w_here, long width, long height, void* data); + +void __fastcall trans_cscale(long i_width, long i_height, long s_width, long s_height, long xy_shift, long w_width, void* data); + // X-Macro for wrapper functions. diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 4b2d479dd..dee31595e 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -18,7 +18,9 @@ // because the compiler builds the better/optimized code when calling the engine functions WRAP_WATCOM_FFUNC4(long, _word_wrap, const char*, text, int, maxWidth, DWORD*, buf, BYTE*, count) WRAP_WATCOM_FFUNC7(long, createWindow, const char*, winName, long, x, long, y, long, width, long, height, long, bgColorIndex, long, flags) -WRAP_WATCOM_FFUNC3(FrmSubframeData*, frame_ptr, FrmFrameData*, frm, long, frame, long, direction) +WRAP_WATCOM_FFUNC3(void, display_inventory, long, inventoryOffset, long, visibleOffset, long, mode) +WRAP_WATCOM_FFUNC4(void, display_target_inventory, long, inventoryOffset, long, visibleOffset, DWORD*, targetInventory, long, mode) +WRAP_WATCOM_FFUNC3(FrmFrameData*, frame_ptr, FrmHeaderData*, frm, long, frame, long, direction) WRAP_WATCOM_FFUNC7(void, make_straight_path_func, fo::GameObject*, objFrom, DWORD, tileFrom, DWORD, tileTo, void*, rotationPtr, DWORD*, result, long, flags, void*, func) WRAP_WATCOM_FFUNC3(long, object_under_mouse, long, crSwitch, long, inclDude, long, elevation) @@ -26,11 +28,12 @@ WRAP_WATCOM_FFUNC3(long, object_under_mouse, long, crSwitch, long, inclDude, lon WRAP_WATCOM_FUNC1(AIcap*, ai_cap, GameObject*, critter) WRAP_WATCOM_FUNC1(Program*, allocateProgram, const char*, filePath) WRAP_WATCOM_FUNC0(void, art_flush) +WRAP_WATCOM_FUNC1(const char*, art_get_name, long, artFID) WRAP_WATCOM_FUNC5(long, art_id, long, artType, long, lstIndex, long, animCode, long, weaponCode, long, directionCode) -WRAP_WATCOM_FUNC3(BYTE*, art_frame_data, FrmFrameData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC3(long, art_frame_width, FrmFrameData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC3(long, art_frame_length, FrmFrameData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC2(FrmFrameData*, art_ptr_lock, long, frmId, DWORD*, lockPtr) +WRAP_WATCOM_FUNC3(BYTE*, art_frame_data, FrmHeaderData*, frm, long, frameNum, long, rotation) +WRAP_WATCOM_FUNC3(long, art_frame_width, FrmHeaderData*, frm, long, frameNum, long, rotation) +WRAP_WATCOM_FUNC3(long, art_frame_length, FrmHeaderData*, frm, long, frameNum, long, rotation) +WRAP_WATCOM_FUNC2(FrmHeaderData*, art_ptr_lock, long, frmId, DWORD*, lockPtr) WRAP_WATCOM_FUNC4(BYTE*, art_ptr_lock_data, long, frmId, long, frameNum, long, rotation, DWORD*, lockPtr) WRAP_WATCOM_FUNC4(BYTE*, art_lock, long, frmId, DWORD*, lockPtr, long*, widthOut, long*, heightOut) WRAP_WATCOM_FUNC1(long, art_ptr_unlock, DWORD, lockId) @@ -42,8 +45,6 @@ WRAP_WATCOM_FUNC3(long, db_freadIntCount, DbFile*, file, DWORD*, dest, long, cou WRAP_WATCOM_FUNC2(long, db_fwriteByte, DbFile*, file, long, value) WRAP_WATCOM_FUNC2(long, db_fwriteInt, DbFile*, file, long, value) WRAP_WATCOM_FUNC0(void, display_stats) -WRAP_WATCOM_FUNC3(void, display_inventory, long, inventoryOffset, long, visibleOffset, long, mode) -WRAP_WATCOM_FUNC4(void, display_target_inventory, long, inventoryOffset, long, visibleOffset, DWORD*, targetInventory, long, mode) // perform combat turn for a given critter WRAP_WATCOM_FUNC2(long, combat_turn, GameObject*, critter, long, isDudeTurn) WRAP_WATCOM_FUNC1(long, critter_is_dead, GameObject*, critter) @@ -56,6 +57,8 @@ WRAP_WATCOM_FUNC1(long, gmouse_3d_set_mode, long, mode) WRAP_WATCOM_FUNC1(long, gmouse_set_cursor, long, picNum) WRAP_WATCOM_FUNC1(Window*, GNW_find, long, winRef) WRAP_WATCOM_FUNC0(long, intface_is_item_right_hand) +// redraws the main game interface windows (useful after changing some data like active hand, etc.) +WRAP_WATCOM_FUNC0(void, intface_redraw) WRAP_WATCOM_FUNC0(void, intface_toggle_item_state) WRAP_WATCOM_FUNC0(void, intface_use_item) WRAP_WATCOM_FUNC0(long, is_pc_sneak_working) @@ -76,6 +79,8 @@ WRAP_WATCOM_FUNC1(long, item_w_max_ammo, GameObject*, item) WRAP_WATCOM_FUNC1(long, item_weight, GameObject*, item) // returns light level at given tile WRAP_WATCOM_FUNC2(long, light_get_tile, long, elevation, long, tileNum) +WRAP_WATCOM_FUNC2(long, load_frame, const char*, filename, FrmFile**, frmPtr) +WRAP_WATCOM_FUNC2(void, MapDirErase, const char*, folder, const char*, ext) WRAP_WATCOM_FUNC2(void, mouse_get_position, long*, outX, long*, outY) WRAP_WATCOM_FUNC0(void, mouse_show) WRAP_WATCOM_FUNC0(void, mouse_hide) @@ -142,6 +147,7 @@ WRAP_WATCOM_FUNC1(void, win_hide, DWORD, winRef) WRAP_WATCOM_FUNC1(BYTE*, win_get_buf, DWORD, winRef) WRAP_WATCOM_FUNC1(void, win_draw, DWORD, winRef) WRAP_WATCOM_FUNC1(void, win_delete, DWORD, winRef) +WRAP_WATCOM_FUNC0(long, windowWidth) WRAP_WATCOM_FUNC1(void, wmCarUseGas, long, gasAmount) WRAP_WATCOM_FUNC0(void, wmPartyWalkingStep) WRAP_WATCOM_FUNC2(DbFile*, xfopen, const char*, fileName, const char*, flags) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 5b67d7e06..58027fd99 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -136,7 +136,7 @@ struct GameObject { return (protoId >> 24); } inline char TypeFid() { - return (artFid >> 24); + return ((artFid >> 24) & 0x0F); } }; @@ -271,45 +271,46 @@ struct ElevatorFrms { #pragma pack(1) struct FrmFile { - long id; //0x00 - long unused; //0x04 - short frames; //0x08 + long id; //0x00 + short fps; //0x04 + short actionFrame; //0x06 + short frames; //0x08 short xshift[6]; //0x0a short yshift[6]; //0x16 - long framestart[6];//0x22 - long size; //0x3a + long oriFrameOffset[6]; //0x22 + long frameAreaSize; //0x3a short width; //0x3e - short height; //0x40 - long frmSize; //0x42 - short xoffset; //0x46 - short yoffset; //0x48 + short height; //0x40 + long frameSize; //0x42 + short xoffset; //0x46 + short yoffset; //0x48 BYTE pixels[80 * 36]; //0x4a }; //structures for holding frms loaded with fallout2 functions #pragma pack(1) -typedef class FrmSubframeData { +typedef class FrmFrameData { // sizeof 12 + 1 byte public: WORD width; WORD height; - DWORD size; + DWORD size; // width * height WORD x; WORD y; BYTE data[1]; // begin frame data -} FrmSubframeData; +} FrmFrameData; #pragma pack(2) -typedef class FrmFrameData { +typedef class FrmHeaderData { // sizeof 62 public: - DWORD version; //version num - WORD fps; //frames per sec + DWORD version; // version num + WORD fps; // frames per sec WORD actionFrame; - WORD numFrames; //number of frames per direction - WORD xCentreShift[6]; //offset from frm centre +=right -=left - WORD yCentreShift[6]; //offset from frm centre +=down -=up - DWORD oriOffset[6]; //frame area offset for diff orientations - DWORD frameAreaSize; -} FrmFrameData; + WORD numFrames; // number of frames per direction + WORD xCentreShift[6]; // shift in the X direction, of frames with orientations [0-5] + WORD yCentreShift[6]; // shift in the Y direction, of frames with orientations [0-5] + DWORD oriOffset[6]; // offset of first frame for direction [0-5] from begining of frame area + DWORD frameAreaSize; // size of all frames area +} FrmHeaderData; #pragma pack(1) struct MessageNode { diff --git a/sfall/FalloutEngine/VariableOffsets.h b/sfall/FalloutEngine/VariableOffsets.h index 508640773..3fd84024f 100644 --- a/sfall/FalloutEngine/VariableOffsets.h +++ b/sfall/FalloutEngine/VariableOffsets.h @@ -43,6 +43,7 @@ #define FO_VAR_curr_font_num 0x51E3B0 #define FO_VAR_curr_pc_stat 0x6681AC #define FO_VAR_curr_stack 0x59E96C +#define FO_VAR_currentWindow 0x51DCB8 #define FO_VAR_cursor_line 0x664514 #define FO_VAR_dialogue_head 0x518850 #define FO_VAR_dialogue_state 0x518714 diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 7fbf1fbbf..d2bb010fd 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -172,16 +172,16 @@ VAR_(sound_music_path1, char*) VAR_(sound_music_path2, char*) VAR_(square, DWORD) VAR_(squares, DWORD*) -VAR_(stack, DWORD) -VAR_(stack_offset, DWORD) +VARA(stack, DWORD, 10) +VARA(stack_offset, DWORD, 10) VARA(stat_data, StatInfo, STAT_real_max_stat) // dynamic array VAR_(stat_flag, DWORD) VAR_(Tag_, DWORD) VAR_(tag_skill, DWORD) VAR_(target_curr_stack, DWORD) -VAR_(target_pud, DWORD) -VAR_(target_stack, DWORD) -VAR_(target_stack_offset, DWORD) +VAR_(target_pud, DWORD*) +VARA(target_stack, DWORD, 10) +VARA(target_stack_offset, DWORD, 10) VAR_(target_str, DWORD) VAR_(target_xpos, DWORD) VAR_(target_ypos, DWORD) diff --git a/sfall/Modules/AI.cpp b/sfall/Modules/AI.cpp index 55c56e9e8..9e4f79c6c 100644 --- a/sfall/Modules/AI.cpp +++ b/sfall/Modules/AI.cpp @@ -26,11 +26,95 @@ namespace sfall { +using namespace fo; +using namespace Fields; -typedef std::unordered_map :: const_iterator iter; +typedef std::unordered_map::const_iterator iter; -static std::unordered_map targets; -static std::unordered_map sources; +static std::unordered_map targets; +static std::unordered_map sources; + +static void __declspec(naked) ai_try_attack_hook_FleeFix() { + __asm { + or byte ptr [esi + combatState], 8; // set new 'ReTarget' flag + jmp fo::funcoffs::ai_run_away_; + } +} + +static const DWORD combat_ai_hook_flee_Ret = 0x42B22F; +static void __declspec(naked) combat_ai_hook_FleeFix() { + __asm { + test byte ptr [ebp], 8; // 'ReTarget' flag (critter.combat_state) + jnz reTarget; + jmp fo::funcoffs::critter_name_; +reTarget: + and byte ptr [ebp], ~(4 | 8); // unset Flee/ReTarget flags + xor edi, edi; + mov dword ptr [esi + whoHitMe], edi; + add esp, 4; + jmp combat_ai_hook_flee_Ret; + } +} + +static const DWORD combat_ai_hack_Ret = 0x42B204; +static void __declspec(naked) combat_ai_hack() { + __asm { + mov edx, [ebx + 0x10]; // cap.min_hp + cmp eax, edx; + jl tryHeal; // curr_hp < min_hp +end: + add esp, 4; + jmp combat_ai_hack_Ret; +tryHeal: + mov eax, esi; + call fo::funcoffs::ai_check_drugs_; + cmp [esi + health], edx; // edx - minimum hp, below which NPC will run away + jge end; + retn; // flee + } +} + +static void __declspec(naked) ai_check_drugs_hook() { + __asm { + call fo::funcoffs::stat_level_; // current hp + mov edx, dword ptr [esp + 0x34 - 0x1C + 4]; // ai cap + mov edx, [edx + 0x10]; // min_hp + cmp eax, edx; // curr_hp < cap.min_hp + cmovl edi, edx; + retn; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +static DWORD RetryCombatLastAP; +static DWORD RetryCombatMinAP; +static void __declspec(naked) RetryCombatHook() { + __asm { + mov RetryCombatLastAP, 0; +retry: + call fo::funcoffs::combat_ai_; +process: + cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; + jle next; + call fo::funcoffs::process_bk_; + jmp process; +next: + 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; + } +} + +//////////////////////////////////////////////////////////////////////////////// static void __fastcall CombatAttackHook(DWORD source, DWORD target) { sources[target] = source; @@ -107,6 +191,26 @@ void AI::init() { MakeJump(0x45F6AF, BlockCombatHook1); // intface_use_item_ HookCall(0x4432A6, BlockCombatHook2); // game_handle_input_ combatBlockedMessage = Translate("sfall", "BlockedCombat", "You cannot enter combat at this time."); + + RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0); + if (RetryCombatMinAP > 0) { + dlog("Applying retry combat patch.", DL_INIT); + HookCall(0x422B94, RetryCombatHook); // combat_turn_ + dlogr(" Done", DL_INIT); + } + + /////////////////////// Combat AI behavior fixes /////////////////////// + + // Fix to allow fleeing NPC to use drugs + MakeCall(0x42B1DC, combat_ai_hack); + // Fix for AI not checking minimum hp properly for using stimpaks (prevents premature fleeing) + HookCall(0x428579, ai_check_drugs_hook); + + // Fix for NPC stuck in fleeing mode when the hit chance of a target was too low + HookCall(0x42B1E3, combat_ai_hook_FleeFix); + HookCalls(ai_try_attack_hook_FleeFix, {0x42ABA8, 0x42ACE5}); + // Disable fleeing when NPC cannot move closer to target + BlockCall(0x42ADF6); // ai_try_attack_ } DWORD _stdcall AIGetLastAttacker(DWORD target) { diff --git a/sfall/Modules/BarBoxes.cpp b/sfall/Modules/BarBoxes.cpp index 64a340212..4a3385f65 100644 --- a/sfall/Modules/BarBoxes.cpp +++ b/sfall/Modules/BarBoxes.cpp @@ -18,6 +18,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" +#include "Graphics.h" #include "LoadGameHook.h" #include "BarBoxes.h" @@ -220,7 +221,7 @@ static void SetEngine(int count) { } static long SetMaxSlots() { - long scrWidth = fo::var::scr_size.offx - (fo::var::scr_size.x + 1); + long scrWidth = Graphics::GetGameWidthRes(); int slots = scrWidth / 127; if (++slots > 16) { diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 02ec7250c..a3d283a7f 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -449,15 +449,12 @@ static void __declspec(naked) partyMemberIncLevels_hook() { static void __declspec(naked) gdProcessUpdate_hack() { __asm { - add eax, esi - cmp eax, dword ptr ds:[FO_VAR_optionRect + 0xC] // _optionRect.offy - jge skip - add eax, 2 - push 0x44702D - retn + lea edx, [eax - 2]; + cmp edx, dword ptr ds:[FO_VAR_optionRect + 0xC]; // _optionRect.offy + jl skip; + mov eax, edx; skip: - push 0x4470DB - retn + retn; } } @@ -804,19 +801,19 @@ static void __declspec(naked) PipStatus_AddHotLines_hook() { static void __declspec(naked) perform_withdrawal_start_display_print_hook() { __asm { - test eax, eax - jz end - jmp fo::funcoffs::display_print_ + test eax, eax; + jz end; + jmp fo::funcoffs::display_print_; end: - retn + retn; } } static void __declspec(naked) op_wield_obj_critter_adjust_ac_hook() { __asm { - call fo::funcoffs::adjust_ac_ - xor eax, eax // not animated - jmp fo::funcoffs::intface_update_ac_ + call fo::funcoffs::adjust_ac_; + xor eax, eax; // not animated + jmp fo::funcoffs::intface_update_ac_; } } @@ -1879,6 +1876,10 @@ static void __declspec(naked) op_use_obj_on_obj_hack() { jz fail; mov edx, [eax + protoId]; shr edx, 24; + cmp dword ptr [esp + 4], eax; // target != source + jne skip; + xor edx, edx; // for calling obj_use_item_on_ instead of action_use_an_item_on_object_ +skip: retn; fail: add esp, 4; @@ -1952,44 +1953,17 @@ static void __declspec(naked) map_age_dead_critters_hack() { } } -static void __declspec(naked) partyFixMultipleMembers_hook() { +static void __declspec(naked) partyFixMultipleMembers_hack() { __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: + cmp esi, edx; + je skip; 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_; + sar eax, 24; + cmp eax, OBJ_TYPE_CRITTER; + jne skip; + test [esi + damageFlags], DAM_DEAD; + cmovnz edx, esi; +skip: retn; } } @@ -2066,10 +2040,63 @@ static void __declspec(naked) exec_script_proc_hack1() { } } +static void __declspec(naked) op_dialogue_reaction_hook() { + __asm { + cmp eax, 4; // neutral fidget + mov eax, 1; + jb good; + je neutral; + jmp bad; +good: + dec eax; +neutral: + dec eax; +bad: + jmp fo::funcoffs::talk_to_critter_reacts_; // -1 - good, 0 - neutral, 1 - bad + } +} + +static void __declspec(naked) obj_pickup_hook() { + __asm { + cmp edi, dword ptr ds:[FO_VAR_obj_dude]; + je dude; + test byte ptr ds:[FO_VAR_combat_state], 1; // in combat? + jz dude; + jmp fo::funcoffs::item_add_force_; +dude: + jmp fo::funcoffs::item_add_mult_; + } +} + +static char pickupMessageBuf[65] = {0}; +static const char* __fastcall GetPickupMessage(const char* name) { + if (pickupMessageBuf[0] == 0) { + Translate("sfall", "NPCPickupFail", "%s cannot pick up the item.", pickupMessageBuf, 64); + } + sprintf(textBuf, pickupMessageBuf, name); + return textBuf; +} + +static void __declspec(naked) obj_pickup_hook_message() { + __asm { + cmp edi, dword ptr ds:[FO_VAR_obj_dude]; + je dude; + mov eax, edi; + call fo::funcoffs::critter_name_; + mov ecx, eax; + call GetPickupMessage; + mov [esp + 0x34 - 0x28 + 4], eax; + mov eax, 1; + retn; +dude: + jmp fo::funcoffs::message_search_; + } +} + void BugFixes::init() { #ifndef NDEBUG - if (isDebug && (GetConfigInt("Debugging", "BugFixes", 1) == 0)) return; + if (isDebug && (GetPrivateProfileIntA("Debugging", "BugFixes", 1, ::sfall::ddrawIni) == 0)) return; #endif // Missing game initialization @@ -2085,7 +2112,7 @@ void BugFixes::init() SafeWrite16(0x46A566, 0x04DB); SafeWrite16(0x46A4E7, 0x04DB); - //if(GetConfigInt("Misc", "SpecialUnarmedAttacksFix", 1)) { + //if (GetConfigInt("Misc", "SpecialUnarmedAttacksFix", 1)) { dlog("Applying Special Unarmed Attacks fix.", DL_INIT); MakeJump(0x42394D, UnarmedAttacksFix); dlogr(" Done", DL_INIT); @@ -2158,11 +2185,11 @@ void BugFixes::init() dlogr(" Done", DL_INIT); // Allow 9 options (lines of text) to be displayed correctly in a dialog window - if (GetConfigInt("Misc", "DialogOptions9Lines", 1)) { + //if (GetConfigInt("Misc", "DialogOptions9Lines", 1)) { dlog("Applying 9 dialog options patch.", DL_INIT); - MakeJump(0x44701C, gdProcessUpdate_hack); + MakeCall(0x447021, gdProcessUpdate_hack, 1); dlogr(" Done", DL_INIT); - } + //} // Fix for "Unlimited Ammo" exploit dlog("Applying fix for Unlimited Ammo exploit.", DL_INIT); @@ -2304,9 +2331,9 @@ void BugFixes::init() MakeCall(0x4130E5, action_explode_hack1); dlogr(" Done", DL_INIT); - // Fix for unable to sell used geiger counters or stealth boys + // Fix for being unable to sell used geiger counters or stealth boys if (GetConfigInt("Misc", "CanSellUsedGeiger", 1)) { - dlog("Applying fix for unable to sell used geiger counters or stealth boys.", DL_INIT); + dlog("Applying fix for being unable to sell used geiger counters or stealth boys.", DL_INIT); SafeWrite8(0x478115, 0xBA); SafeWrite8(0x478138, 0xBA); MakeJump(0x474D22, barter_attempt_transaction_hack); @@ -2471,7 +2498,7 @@ void BugFixes::init() SafeWrite8(0x4C1015, 0x90); HookCall(0x4C1042, wmSetupRandomEncounter_hook); - // Fix for unable to sell/give items in the barter screen when the player/party member is overloaded + // Fix for being 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 @@ -2551,6 +2578,8 @@ void BugFixes::init() } // Fix crash when calling use_obj/use_obj_on_obj without using set_self in global scripts + // also change the behavior of use_obj_on_obj function + // if the object uses the item on itself, then another function is called (not a bug fix) MakeCall(0x45C376, op_use_obj_on_obj_hack, 1); MakeCall(0x456A92, op_use_obj_hack, 1); @@ -2591,7 +2620,7 @@ void BugFixes::init() dlogr(" Done", DL_INIT); // Fix for the removal of party member's corpse when loading the map - HookCall(0x4957B8, partyFixMultipleMembers_hook); + MakeCall(0x495769, partyFixMultipleMembers_hack, 1); // Fix for unexplored areas being revealed on the automap when entering a map MakeCall(0x48A76B, obj_move_to_tile_hack_seen, 1); @@ -2615,6 +2644,14 @@ void BugFixes::init() // Fix for the start procedure not being called correctly if the required standard script procedure is missing MakeCall(0x4A4926, exec_script_proc_hack); MakeCall(0x4A4979, exec_script_proc_hack1); + + // Fix the argument value of dialogue_reaction function + HookCall(0x456FFA, op_dialogue_reaction_hook); + + // Fix for NPC stuck in a loop of picking up items in combat and the incorrect message being displayed when the NPC cannot pick + // up an item due to not enough space in the inventory + HookCall(0x49B6E7, obj_pickup_hook); + HookCall(0x49B71C, obj_pickup_hook_message); } } diff --git a/sfall/Modules/Combat.cpp b/sfall/Modules/Combat.cpp index 24e22fa6b..64f1141d9 100644 --- a/sfall/Modules/Combat.cpp +++ b/sfall/Modules/Combat.cpp @@ -57,7 +57,7 @@ static DWORD __fastcall add_check_for_item_ammo_cost(register fo::GameObject* we rounds = fo::func::item_w_rounds(weapon); // ammo in burst } if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { - AmmoCostHook_Script(1, weapon, &rounds); // get rounds cost from hook + AmmoCostHook_Script(1, weapon, rounds); // get rounds cost from hook } else if (rounds == 1) { fo::func::item_w_compute_ammo_cost(weapon, &rounds); } @@ -125,7 +125,7 @@ static DWORD __fastcall divide_burst_rounds_by_ammo_cost(fo::GameObject* weapon, if (HookScripts::IsInjectHook(HOOK_AMMOCOST)) { rounds = burstRounds; // rounds in burst - AmmoCostHook_Script(2, weapon, &rounds); + AmmoCostHook_Script(2, weapon, rounds); } DWORD cost = burstRounds * rounds; // so much ammo is required for this burst diff --git a/sfall/Modules/Console.cpp b/sfall/Modules/Console.cpp index 1648ad8fc..f6246b7e0 100644 --- a/sfall/Modules/Console.cpp +++ b/sfall/Modules/Console.cpp @@ -36,10 +36,10 @@ static void _stdcall ConsoleFilePrint(const char* msg) { static const DWORD ConsoleHookRet = 0x431871; static void __declspec(naked) ConsoleHook() { __asm { - pushad; + pushadc; push eax; call ConsoleFilePrint; - popad; + popadc; push ebx; push ecx; push edx; @@ -51,7 +51,7 @@ static void __declspec(naked) ConsoleHook() { void Console::init() { auto path = GetConfigString("Misc", "ConsoleOutputPath", "", MAX_PATH); - if (path.size() > 0) { + if (!path.empty()) { consolefile.open(path); if (consolefile.is_open()) { MakeJump(0x43186C, ConsoleHook); diff --git a/sfall/Modules/DamageMod.cpp b/sfall/Modules/DamageMod.cpp index 953399dd8..2eb383add 100644 --- a/sfall/Modules/DamageMod.cpp +++ b/sfall/Modules/DamageMod.cpp @@ -32,7 +32,7 @@ int DamageMod::formula; // Damage Fix v5 (with v5.1 Damage Multiplier tweak) by Glovz 2014.04.16.xx.xx // TODO: rewrite in C++ -void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty) { +void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty) { if (rounds <= 0) return; int ammoY = fo::func::item_w_dam_div(ctd.weapon); // ammoY value (divisor) @@ -231,7 +231,7 @@ void DamageMod::DamageGlovz(fo::ComputeAttackResult &ctd, DWORD* accumulatedDama } // YAAM -void DamageMod::DamageYAAM(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty) { +void DamageMod::DamageYAAM(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty) { int ammoDiv = fo::func::item_w_dam_div(ctd.weapon); // Retrieve Ammo Divisor int ammoMult = fo::func::item_w_dam_mult(ctd.weapon); // Retrieve Ammo Dividend @@ -277,7 +277,7 @@ void DamageMod::DamageYAAM(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamag resistedDamage /= 100; // Resisted Damage = Resisted Damage / 100 rawDamage -= resistedDamage; // Raw Damage = Raw Damage - Resisted Damage - if (rawDamage > 0) *accumulatedDamage += rawDamage; // Accumulated Damage = Accumulated Damage + Raw Damage + if (rawDamage > 0) accumulatedDamage += rawDamage; // Accumulated Damage = Accumulated Damage + Raw Damage } } //////////////////////////////////////////////////////////////////////////////// diff --git a/sfall/Modules/DamageMod.h b/sfall/Modules/DamageMod.h index 1589a3973..91d8ec267 100644 --- a/sfall/Modules/DamageMod.h +++ b/sfall/Modules/DamageMod.h @@ -29,8 +29,8 @@ class DamageMod : public Module { void init(); static int formula; - static void DamageGlovz(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty); - static void DamageYAAM(fo::ComputeAttackResult &ctd, DWORD* accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty); + static void DamageGlovz(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage, int multiplyDamage, int difficulty); + static void DamageYAAM(fo::ComputeAttackResult &ctd, DWORD &accumulatedDamage, int rounds, int armorDT, int armorDR, int bonusRangedDamage,int multiplyDamage, int difficulty); }; } diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index d67a25860..0082e0a1c 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -267,14 +267,14 @@ void RunDebugEditor() { WSACleanup(); } +static const DWORD dbg_error_ret = 0x453FD8; static void __declspec(naked) dbg_error_hack() { __asm { cmp ebx, 1; je hide; sub esp, 0x104; - retn; + jmp dbg_error_ret; hide: - add esp, 4; // destroy this return addr pop esi; pop ecx; retn; @@ -313,6 +313,9 @@ void DebugModePatch() { } else { SafeWrite32(0x4C6D9C, (DWORD)debugGnw); } + if (GetPrivateProfileIntA("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { + MakeJump(0x453FD2, dbg_error_hack); + } dlogr(" Done", DL_INIT); } } @@ -338,10 +341,6 @@ void DebugEditor::init() { } }; } - - if (GetPrivateProfileIntA("Debugging", "HideObjIsNullMsg", 0, ::sfall::ddrawIni)) { - MakeCall(0x453FD2, dbg_error_hack, 1); - } } } diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index 179a61ffd..4792c753a 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -32,13 +32,13 @@ #include #include "..\main.h" - #include "..\FalloutEngine\Fallout2.h" #include "..\InputFuncs.h" #include "..\Version.h" +#include "LoadGameHook.h" +#include "ScriptShaders.h" #include "Graphics.h" -#include "LoadGameHook.h" namespace sfall { @@ -47,7 +47,6 @@ typedef HRESULT (_stdcall *DDrawCreateProc)(void*, IDirectDraw**, void*); typedef IDirect3D9* (_stdcall *D3DCreateProc)(UINT version); #define UNUSEDFUNCTION { DEBUGMESS("\n[SFALL] Unused function called: %s", __FUNCTION__); return DDERR_GENERIC; } -#define SAFERELEASE(a) { if (a) { a->Release(); a = 0; } } static DWORD ResWidth; static DWORD ResHeight; @@ -109,36 +108,37 @@ static const char* gpuEffect= // shader for displaying head textures "float4 P1( in float2 Tex : TEXCOORD0 ) : COLOR0 {" - " float backdrop = tex2D(s0, Tex).a;" - " float3 result;" - " if(abs(backdrop - 1.0) < 0.001) {" // (48.0 / 255.0) // 48 - key index color - " result = tex2D(s2, saturate((Tex - corner) / size));" - " } else {" - " result = tex1D(s1, backdrop);" - " result = float3(result.b, result.g, result.r);" - " }" + "float backdrop = tex2D(s0, Tex).a;" + "float3 result;" + "if (abs(backdrop - 1.0) < 0.001) {" // (48.0 / 255.0) // 48 - key index color + "result = tex2D(s2, saturate((Tex - corner) / size));" + "} else {" + "result = tex1D(s1, backdrop);" + "result = float3(result.b, result.g, result.r);" + "}" // blend highlights "if (showhl) {" "float4 h = tex2D(s3, saturate((Tex - cornerhl) / sizehl));" "result = saturate(result + h.rgb);" // saturate(result * (1 - h.a) * h.rgb * h.a)" "}" - " return float4(result.r, result.g, result.b, 1);" + "return float4(result.r, result.g, result.b, 1);" "}" "technique T1" "{" - " pass p1 { PixelShader = compile ps_2_0 P1(); }" + "pass p1 { PixelShader = compile ps_2_0 P1(); }" "}" "float4 P0( in float2 Tex : TEXCOORD0 ) : COLOR0 {" - " float3 result = tex1D(s1, tex2D(s0, Tex).a);" - " return float4(result.b, result.g, result.r, 1);" + "float3 result = tex1D(s1, tex2D(s0, Tex).a);" + "return float4(result.b, result.g, result.r, 1);" "}" "technique T0" "{" - " pass p0 { PixelShader = compile ps_2_0 P0(); }" - "}"; + "pass p0 { PixelShader = compile ps_2_0 P0(); }" + "}" +; static D3DXHANDLE gpuBltBuf; static D3DXHANDLE gpuBltPalette; @@ -152,25 +152,6 @@ static D3DXHANDLE gpuBltShowHighlight; static float rcpres[2]; -struct sShader { - ID3DXEffect* Effect; - bool Active; - D3DXHANDLE ehTicks; - DWORD mode; - DWORD mode2; - - sShader() { - Effect=0; - Active=false; - ehTicks=0; - mode=0; - mode2=0; - } -}; - -static std::vector shaders; -static std::vector shaderTextures; - #define MYVERTEXFORMAT D3DFVF_XYZRHW|D3DFVF_TEX1 struct MyVertex { float x,y,z,w,u,v; @@ -206,94 +187,8 @@ static void rcpresInit() { rcpres[1] = 1.0f / (float)Graphics::GetGameHeightRes(); } -void _stdcall SetShaderMode(DWORD d, DWORD mode) { - if (d >= shaders.size() || !shaders[d].Effect) return; - if (mode & 0x80000000) { - shaders[d].mode2 = mode ^ 0x80000000; - } else { - shaders[d].mode = mode; - } -} - -int _stdcall LoadShader(const char* path) { - if (!Graphics::mode || strstr(path, "..") || strstr(path, ":")) return -1; - char buf[MAX_PATH]; - sprintf(buf, "%s\\shaders\\%s", fo::var::patches, path); - for (DWORD d = 0; d < shaders.size(); d++) { - if (!shaders[d].Effect) { - if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shaders[d].Effect, 0))) return -1; - else return d; - } - } - sShader shader = sShader(); - if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shader.Effect, 0))) return -1; - - shader.Effect->SetFloatArray("rcpres", rcpres, 2); - - for (int i = 1; i < 128; i++) { - const char* name; - IDirect3DTexture9* tex; - - sprintf_s(buf, "texname%d", i); - if (FAILED(shader.Effect->GetString(buf, &name))) break; - sprintf_s(buf, "%s\\art\\stex\\%s", fo::var::patches, name); - if (FAILED(D3DXCreateTextureFromFileA(d3d9Device, buf, &tex))) continue; - sprintf_s(buf, "tex%d", i); - shader.Effect->SetTexture(buf, tex); - shaderTextures.push_back(tex); - } - - shader.ehTicks = shader.Effect->GetParameterByName(0, "tickcount"); - shaders.push_back(shader); - return shaders.size() - 1; -} - -void _stdcall ActivateShader(DWORD d) { - if (d < shaders.size() && shaders[d].Effect) shaders[d].Active = true; -} - -void _stdcall DeactivateShader(DWORD d) { - if (d < shaders.size()) shaders[d].Active = false; -} - -int _stdcall GetShaderTexture(DWORD d, DWORD id) { - if (id < 1 || id > 128 || d >= shaders.size() || !shaders[d].Effect) return -1; - IDirect3DBaseTexture9* tex = 0; - char buf[8] = "tex"; - _itoa_s(id, &buf[3], 4, 10); - if (FAILED(shaders[d].Effect->GetTexture(buf, &tex)) || !tex) return -1; - tex->Release(); - for (DWORD i = 0; i < shaderTextures.size(); i++) { - if (shaderTextures[i] == tex) return i; - } - return -1; -} - -void _stdcall FreeShader(DWORD d) { - if (d < shaders.size()) { - SAFERELEASE(shaders[d].Effect); - shaders[d].Active = false; - } -} - -void _stdcall SetShaderInt(DWORD d, const char* param, int value) { - if (d >= shaders.size() || !shaders[d].Effect) return; - shaders[d].Effect->SetInt(param, value); -} - -void _stdcall SetShaderFloat(DWORD d, const char* param, float value) { - if (d >= shaders.size() || !shaders[d].Effect) return; - shaders[d].Effect->SetFloat(param, value); -} - -void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4) { - if (d >= shaders.size() || !shaders[d].Effect) return; - shaders[d].Effect->SetFloatArray(param, &f1, 4); -} - -void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value) { - if (d >= shaders.size() || !shaders[d].Effect || value >= shaderTextures.size()) return; - shaders[d].Effect->SetTexture(param, shaderTextures[value]); +const float* Graphics::rcpresGet() { + return rcpres; } static void ResetDevice(bool CreateNew) { @@ -344,9 +239,7 @@ static void ResetDevice(bool CreateNew) { } else { d3d9Device->Reset(¶ms); if (gpuBltEffect) gpuBltEffect->OnResetDevice(); - for (DWORD d = 0; d < shaders.size(); d++) { - if (shaders[d].Effect) shaders[d].Effect->OnResetDevice(); - } + ScriptShaders::OnResetDevice(); } ShaderVertices[1].y = ResHeight - 0.5f; @@ -469,9 +362,7 @@ static void Present() { SAFERELEASE(movieBuffer); SAFERELEASE(gpuPalette); if (gpuBltEffect) gpuBltEffect->OnLostDevice(); - for (DWORD d = 0; d < shaders.size(); d++) { - if (shaders[d].Effect) shaders[d].Effect->OnLostDevice(); - } + ScriptShaders::OnLostDevice(); DeviceLost = true; } } @@ -484,7 +375,7 @@ void RefreshGraphics() { d3d9Device->SetStreamSource(0, vBuffer, 0, sizeof(MyVertex)); d3d9Device->SetRenderTarget(0, sSurf1); - if (Graphics::GPUBlt && shaders.size()) { + if (Graphics::GPUBlt && ScriptShaders::Count()) { UINT unused; gpuBltEffect->Begin(&unused, 0); gpuBltEffect->BeginPass(0); @@ -496,28 +387,11 @@ void RefreshGraphics() { } else { d3d9Device->SetTexture(0, Tex); } - for (int d = shaders.size() - 1; d >= 0; d--) { - if (!shaders[d].Effect || !shaders[d].Active) continue; - if (shaders[d].mode2 && !(shaders[d].mode2 & GetLoopFlags())) continue; - if (shaders[d].mode&GetLoopFlags()) continue; - - if (shaders[d].ehTicks) shaders[d].Effect->SetInt(shaders[d].ehTicks, GetTickCount()); - UINT passes; - shaders[d].Effect->Begin(&passes, 0); - for (DWORD pass = 0; pass < passes; pass++) { - shaders[d].Effect->BeginPass(pass); - d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); - shaders[d].Effect->EndPass(); - d3d9Device->StretchRect(sSurf1, 0, sSurf2, 0, D3DTEXF_NONE); - d3d9Device->SetTexture(0, sTex2); - } - shaders[d].Effect->End(); - d3d9Device->SetTexture(0, sTex2); - } + ScriptShaders::Refresh(sSurf1, sSurf2, sTex2); d3d9Device->SetStreamSource(0, vBuffer2, 0, sizeof(MyVertex)); d3d9Device->SetRenderTarget(0, backbuffer); - if (Graphics::GPUBlt && !shaders.size()) { + if (Graphics::GPUBlt && !ScriptShaders::Count()) { UINT unused; gpuBltEffect->Begin(&unused, 0); gpuBltEffect->BeginPass(0); @@ -629,11 +503,6 @@ void Graphics::SetDefaultTechnique() { gpuBltEffect->SetTechnique("T0"); } -void GraphicsResetOnGameLoad() { - for (DWORD d = 0; d < shaders.size(); d++) SAFERELEASE(shaders[d].Effect); - shaders.clear(); -} - class FakePalette2 : IDirectDrawPalette { private: ULONG Refs; @@ -964,10 +833,7 @@ class FakeDirectDraw2 : IDirectDraw ULONG _stdcall Release() { if (!--Refs) { - for (DWORD d = 0; d < shaders.size(); d++) SAFERELEASE(shaders[d].Effect); - for (DWORD d = 0; d < shaderTextures.size(); d++) shaderTextures[d]->Release(); - shaders.clear(); - shaderTextures.clear(); + ScriptShaders::Release(); SAFERELEASE(backbuffer); SAFERELEASE(sSurf1); SAFERELEASE(sSurf2); @@ -1140,7 +1006,6 @@ void Graphics::init() { } if (Graphics::mode) { - LoadGameHook::OnGameReset() += GraphicsResetOnGameLoad; LoadGameHook::OnAfterGameInit() += rcpresInit; } } diff --git a/sfall/Modules/Graphics.h b/sfall/Modules/Graphics.h index cf0b8226e..421391519 100644 --- a/sfall/Modules/Graphics.h +++ b/sfall/Modules/Graphics.h @@ -27,6 +27,8 @@ namespace sfall { +#define SAFERELEASE(a) { if (a) { a->Release(); a = 0; } } + class Graphics : public Module { public: const char* name() { return "Graphics"; } @@ -39,25 +41,18 @@ class Graphics : public Module { static long GetGameWidthRes(); static long GetGameHeightRes(); + static const float* rcpresGet(); + static void SetHighlightTexture(IDirect3DTexture9* htex); static void SetHeadTex(IDirect3DTexture9* tex, int width, int height, int xoff, int yoff, int showHighlight); static void SetHeadTechnique(); static void SetDefaultTechnique(); }; -int _stdcall GetShaderVersion(); -int _stdcall LoadShader(const char*); -void _stdcall ActivateShader(DWORD); -void _stdcall DeactivateShader(DWORD); -void _stdcall FreeShader(DWORD); -void _stdcall SetShaderMode(DWORD d, DWORD mode); +extern IDirect3D9* d3d9; +extern IDirect3DDevice9* d3d9Device; -void _stdcall SetShaderInt(DWORD d, const char* param, int value); -void _stdcall SetShaderFloat(DWORD d, const char* param, float value); -void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4); - -int _stdcall GetShaderTexture(DWORD d, DWORD id); -void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value); +int _stdcall GetShaderVersion(); void RefreshGraphics(); void GetFalloutWindowInfo(DWORD* width, DWORD* height, HWND* window); diff --git a/sfall/Modules/HeroAppearance.cpp b/sfall/Modules/HeroAppearance.cpp index fd638c064..8fbd8ca78 100644 --- a/sfall/Modules/HeroAppearance.cpp +++ b/sfall/Modules/HeroAppearance.cpp @@ -649,7 +649,7 @@ static void sub_draw(long subWidth, long subHeight, long fromWidth, long fromHei static void DrawBody(DWORD critNum, BYTE* surface) { DWORD critFrmLock; - fo::FrmFrameData *critFrm = fo::func::art_ptr_lock(BuildFrmId(1, critNum), &critFrmLock); + fo::FrmHeaderData *critFrm = fo::func::art_ptr_lock(BuildFrmId(1, critNum), &critFrmLock); DWORD critWidth = fo::func::art_frame_width(critFrm, 0, charRotOri); DWORD critHeight = fo::func::art_frame_length(critFrm, 0, charRotOri); BYTE *critSurface = fo::func::art_frame_data(critFrm, 0, charRotOri); diff --git a/sfall/Modules/HookScripts.h b/sfall/Modules/HookScripts.h index 61485585a..f36c3b859 100644 --- a/sfall/Modules/HookScripts.h +++ b/sfall/Modules/HookScripts.h @@ -98,7 +98,7 @@ void HookScriptClear(); void LoadHookScripts(); extern DWORD initingHookScripts; -extern int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD* rounds); +extern int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds); void _stdcall RunHookScriptsAtProc(DWORD procId); } diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index 7ca4d8d3d..6b62731cd 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -178,7 +178,7 @@ static void __declspec(naked) ComputeDamageHook() { } } -static void __fastcall SubComputeDamageHook_Script(fo::ComputeAttackResult &ctd, DWORD armorDR, DWORD* accumulatedDamage, DWORD armorDT, DWORD bonusRangedDamage, DWORD rounds, DWORD multiplyDamage, DWORD difficulty) { +static void __fastcall SubComputeDamageHook_Script(fo::ComputeAttackResult &ctd, DWORD armorDR, DWORD &accumulatedDamage, DWORD armorDT, DWORD bonusRangedDamage, DWORD rounds, DWORD multiplyDamage, DWORD difficulty) { // calculated internal ammo mod damage switch (DamageMod::formula) { case 1: @@ -203,12 +203,12 @@ static void __fastcall SubComputeDamageHook_Script(fo::ComputeAttackResult &ctd, args[7] = bonusRangedDamage; args[8] = multiplyDamage; args[9] = difficulty; - args[10] = *accumulatedDamage; + args[10] = accumulatedDamage; args[11] = (DWORD)&ctd; RunHookScript(HOOK_SUBCOMBATDAMAGE); - if (cRet == 1) *accumulatedDamage = rets[0]; + if (cRet == 1) accumulatedDamage = rets[0]; EndHook(); } @@ -295,27 +295,27 @@ static void __declspec(naked) ItemDamageHook() { _asm jmp fo::funcoffs::roll_random_; } -int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD* rounds) { +int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD &rounds) { int result = 0; BeginHook(); argCount = 4; args[0] = (DWORD)weapon; - args[1] = *rounds; // rounds in attack + args[1] = rounds; // rounds in attack args[3] = hookType; if (hookType == 2) { // burst hook - *rounds = 1; // set default multiply for check burst attack + rounds = 1; // set default multiply for check burst attack } else { - result = fo::func::item_w_compute_ammo_cost(weapon, rounds); + result = fo::func::item_w_compute_ammo_cost(weapon, &rounds); if (result == -1) goto failed; // failed computed } - args[2] = *rounds; // rounds as computed by game (cost) + args[2] = rounds; // rounds as computed by game (cost) RunHookScript(HOOK_AMMOCOST); - if (cRet > 0) *rounds = rets[0]; // override rounds + if (cRet > 0) rounds = rets[0]; // override rounds failed: EndHook(); @@ -340,7 +340,7 @@ static void __declspec(naked) AmmoCostHook() { } // hooks combat_turn function -static void _declspec(naked) CombatTurnHook() { +static void __declspec(naked) CombatTurnHook() { __asm { HookBegin; mov args[0], 1; // turn begin @@ -380,7 +380,7 @@ static void _declspec(naked) CombatTurnHook() { // hack to exit from combat_add_noncoms function without crashing when you load game during NPC turn static const DWORD CombatHack_add_noncoms_back = 0x422359; -static void _declspec(naked) CombatAddNoncoms_CombatTurnHack() { +static void __declspec(naked) CombatAddNoncoms_CombatTurnHack() { __asm { call CombatTurnHook; cmp eax, -1; @@ -415,7 +415,7 @@ fo::GameObject* __fastcall ComputeExplosionOnExtrasHook_Script(fo::GameObject* o return result; } -static void _declspec(naked) ComputeExplosionOnExtrasHook() { +static void __declspec(naked) ComputeExplosionOnExtrasHook() { __asm { cmp dword ptr [esp + 0x34 + 4], 0x429533; // skip hook when AI assesses the situation in choosing the best weapon jz end; diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index 2142f8618..bf8ff0547 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -64,23 +64,6 @@ static void __declspec(naked) MoveCostHook() { } } -/* Common inventory move hook */ -static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, int type) { - BeginHook(); - argCount = 3; - - args[0] = type; // event type - args[1] = item; // item being dropped - args[2] = itemReplace; // item being replaced here - - RunHookScript(HOOK_INVENTORYMOVE); - - int result = (cRet > 0) ? rets[0] : -1; - EndHook(); - - return result; -} - static int __fastcall SwitchHandHook_Script(fo::GameObject* item, fo::GameObject* itemReplaced, DWORD addr) { if (itemReplaced && fo::GetItemType(itemReplaced) == fo::item_type_weapon && fo::GetItemType(item) == fo::item_type_ammo) { return -1; // to prevent inappropriate hook call after dropping ammo on weapon @@ -109,58 +92,75 @@ static int __fastcall SwitchHandHook_Script(fo::GameObject* item, fo::GameObject This hook is called every time an item is placed into either hand slot via inventory screen drag&drop If switch_hand_ function is not called, item is not placed anywhere (it remains in main inventory) */ -static void _declspec(naked) SwitchHandHook() { +static void __declspec(naked) SwitchHandHook() { __asm { - pushad; + pushadc; mov ecx, eax; // item being moved mov edx, [edx]; // other item - mov eax, [esp + 32]; // back address + mov eax, [esp + 12]; // back address push eax; call SwitchHandHook_Script; cmp eax, -1; // ret value - popad; + popadc; jne skip; - call fo::funcoffs::switch_hand_; + jmp fo::funcoffs::switch_hand_; skip: retn; } } +/* Common inventory move hook */ +static int __fastcall InventoryMoveHook_Script(DWORD itemReplace, DWORD item, int type) { + BeginHook(); + argCount = 3; + + args[0] = type; // event type + args[1] = item; // item being dropped + args[2] = itemReplace; // item being replaced here + + RunHookScript(HOOK_INVENTORYMOVE); + + int result = (cRet > 0) ? rets[0] : -1; + EndHook(); + + return result; +} + static const DWORD UseArmorHack_back = 0x4713AF; // normal operation (old 0x4713A9) static const DWORD UseArmorHack_skip = 0x471481; // skip code, prevent wearing armor // This hack is called when an armor is dropped into the armor slot at inventory screen -static void _declspec(naked) UseArmorHack() { +static void __declspec(naked) UseArmorHack() { __asm { mov ecx, ds:[FO_VAR_i_worn]; // replacement item (override code) mov edx, [esp + 0x58 - 0x40]; // item - pushad; + push ecx; push 3; // event: armor slot - call InventoryMoveHook_Script; + call InventoryMoveHook_Script; // ecx - replacement item cmp eax, -1; // ret value - popad; - jne skip; - jmp UseArmorHack_back; + pop ecx; + jne skip; + jmp UseArmorHack_back; skip: - jmp UseArmorHack_skip; + jmp UseArmorHack_skip; } } -static void _declspec(naked) MoveInventoryHook() { +static void __declspec(naked) MoveInventoryHook() { __asm { - pushad; - xor ecx, ecx; // no item replace - mov ebx, ecx; + pushadc; + xor eax, eax; + mov ecx, eax; // no item replace cmp dword ptr ds:[FO_VAR_curr_stack], 0; jle noCont; mov ecx, eax; // contaner ptr - mov ebx, 5; + mov eax, 5; noCont: - push ebx; // event: 0 - main backpack, 5 - contaner + push eax; // event: 0 - main backpack, 5 - contaner call InventoryMoveHook_Script; // edx - item cmp eax, -1; // ret value - popad; + popadc; jne skip; - call fo::funcoffs::item_add_force_; + jmp fo::funcoffs::item_add_force_; skip: retn; } @@ -266,7 +266,7 @@ static void __declspec(naked) DropIntoContainerHandSlotHack() { //static const DWORD DropAmmoIntoWeaponHack_back = 0x47658D; // proceed with reloading static const DWORD DropAmmoIntoWeaponHack_return = 0x476643; -static void _declspec(naked) DropAmmoIntoWeaponHook() { +static void __declspec(naked) DropAmmoIntoWeaponHook() { __asm { pushadc; mov ecx, ebp; // weapon ptr @@ -307,6 +307,27 @@ static void __declspec(naked) PickupObjectHack() { } } +static void __declspec(naked) InvenPickupHook() { + __asm { + call fo::funcoffs::mouse_click_in_; + test eax, eax; + jnz runHook; + retn; +runHook: + cmp dword ptr ds:[FO_VAR_curr_stack], 0; + jnz skip; + mov edx, [esp + 0x58 - 0x40 + 4]; // item + xor ecx, ecx; // no itemReplace + push 8; // event: drop item on character portrait + call InventoryMoveHook_Script; + cmp eax, -1; // ret value + je skip; + xor eax, eax; // 0 - cancel, otherwise engine handler +skip: + retn; + } +} + /* Common InvenWield hook */ static bool InvenWieldHook_Script(int flag) { argCount = 4; @@ -320,7 +341,7 @@ static bool InvenWieldHook_Script(int flag) { return result; // True - use engine handler } -static void _declspec(naked) InvenWieldFuncHook() { +static void __declspec(naked) InvenWieldFuncHook() { using namespace fo; __asm { HookBegin; @@ -349,7 +370,7 @@ static void _declspec(naked) InvenWieldFuncHook() { } // called when unwielding weapons -static void _declspec(naked) InvenUnwieldFuncHook() { +static void __declspec(naked) InvenUnwieldFuncHook() { __asm { HookBegin; mov args[0], eax; // critter @@ -376,7 +397,7 @@ static void _declspec(naked) InvenUnwieldFuncHook() { } } -static void _declspec(naked) CorrectFidForRemovedItemHook() { +static void __declspec(naked) CorrectFidForRemovedItemHook() { __asm { HookBegin; mov args[0], eax; // critter @@ -453,6 +474,8 @@ void Inject_InventoryMoveHook() { SafeWrite32(0x49B665, 0x850FD285); // test edx, edx SafeWrite32(0x49B669, 0xC2); // jnz 0x49B72F SafeWrite8(0x49B66E, 0xFE); // cmp edi > cmp esi + + HookCall(0x471457, InvenPickupHook); } void Inject_InvenWieldHook() { diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 8b7c964c1..7a0578a6a 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -474,13 +474,13 @@ static void __declspec(naked) RestTimerEscapeHook() { } } -static int __fastcall ExplosiveTimerHook_Script(DWORD* type, DWORD item, DWORD time) { +static int __fastcall ExplosiveTimerHook_Script(DWORD &type, DWORD item, DWORD time) { BeginHook(); argCount = 3; args[0] = time; args[1] = item; - args[2] = (*type == 11) ? fo::ROLL_FAILURE : fo::ROLL_SUCCESS; + args[2] = (type == 11) ? fo::ROLL_FAILURE : fo::ROLL_SUCCESS; RunHookScript(HOOK_EXPLOSIVETIMER); @@ -490,7 +490,7 @@ static int __fastcall ExplosiveTimerHook_Script(DWORD* type, DWORD item, DWORD t if (cRet > 1) { int typeRet = rets[1]; if (typeRet >= fo::ROLL_CRITICAL_FAILURE && typeRet <= fo::ROLL_CRITICAL_SUCCESS) { - *type = (typeRet > fo::ROLL_FAILURE) ? 8 : 11; // returned new type (8 = SUCCESS, 11 = FAILURE) + type = (typeRet > fo::ROLL_FAILURE) ? 8 : 11; // returned new type (8 = SUCCESS, 11 = FAILURE) } } } @@ -498,7 +498,7 @@ static int __fastcall ExplosiveTimerHook_Script(DWORD* type, DWORD item, DWORD t return time; } -static void _declspec(naked) ExplosiveTimerHook() { +static void __declspec(naked) ExplosiveTimerHook() { __asm { push eax; // time in ticks for queue_add_ push edx; diff --git a/sfall/Modules/HookScripts/ObjectHs.cpp b/sfall/Modules/HookScripts/ObjectHs.cpp index 62268cecd..5e3727c0f 100644 --- a/sfall/Modules/HookScripts/ObjectHs.cpp +++ b/sfall/Modules/HookScripts/ObjectHs.cpp @@ -169,24 +169,24 @@ static void __declspec(naked) DescriptionObjHook() { } } -static bool __fastcall SetLightingHook_Script(DWORD* intensity, DWORD* radius, DWORD object) { +static bool __fastcall SetLightingHook_Script(DWORD &intensity, DWORD &radius, DWORD object) { BeginHook(); argCount = 3; args[0] = object; - args[1] = *intensity; - args[2] = *radius; + args[1] = intensity; + args[2] = radius; RunHookScript(HOOK_SETLIGHTING); bool result = false; if (cRet > 0) { int light = rets[0]; if (light < 0) light = 0; - *intensity = light; + intensity = light; if (cRet > 1 && object != -1) { int dist = rets[1]; if (dist < 0) dist = 0; - *radius = dist; + radius = dist; } result = true; } diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index 5666dbae0..85b9b5538 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -63,6 +63,7 @@ void InventoryKeyPressedHook(DWORD dxKey, bool pressed) { } ///////////////////////////////////////////////////////////////// + DWORD __stdcall sf_item_total_size(fo::GameObject* critter) { int totalSize = fo::func::item_c_curr_size(critter); @@ -85,19 +86,6 @@ DWORD __stdcall sf_item_total_size(fo::GameObject* critter) { return totalSize; } -/*static const DWORD ObjPickupFail=0x49B70D; -static const DWORD ObjPickupEnd=0x49B6F8; -static __declspec(naked) void ObjPickupHook() { - __asm { - cmp edi, ds:[FO_VAR_obj_dude]; - jnz end; -end: - lea edx, [esp+0x10]; - mov eax, ecx; - jmp ObjPickupEnd; - } -}*/ - static int __stdcall CritterGetMaxSize(fo::GameObject* critter) { if (critter == fo::var::obj_dude) return invSizeMaxLimit; @@ -198,8 +186,8 @@ static __declspec(naked) void barter_attempt_transaction_hack_pc() { /* cmp eax, edx */ jg fail; // if there's no available weight //------ - mov ecx, edi; // source (pc) - mov edx, ebp; // npc table + mov ecx, edi; // source (pc) + mov edx, ebp; // npc table call BarterAttemptTransaction; test eax, eax; jz fail; @@ -232,19 +220,19 @@ static __declspec(naked) void barter_attempt_transaction_hack_pm() { static __declspec(naked) void loot_container_hook_btn() { __asm { push ecx; - push edx; // source current weight - mov edx, eax; // target - mov ecx, [esp + 0x150 - 0x1C + 12]; // source + push edx; // source current weight + mov edx, eax; // target + mov ecx, [esp + 0x150 - 0x1C + 12]; // source call BarterAttemptTransaction; pop edx; pop ecx; test eax, eax; jz fail; - mov eax, ebp; // target + mov eax, ebp; // target jmp fo::funcoffs::item_total_weight_; fail: mov eax, edx; - inc eax; // weight + 1 + inc eax; // weight + 1 retn; } } @@ -340,51 +328,32 @@ static void __declspec(naked) gdControlUpdateInfo_hack() { ///////////////////////////////////////////////////////////////// static std::string superStimMsg; -static int __fastcall SuperStimFix2(fo::GameObject* item, fo::GameObject* target) { +static int __fastcall SuperStimFix(fo::GameObject* item, fo::GameObject* target) { if (item->protoId != fo::PID_SUPER_STIMPAK || !target || target->Type() != fo::OBJ_TYPE_CRITTER) { return 0; } - long curr_hp, max_hp; - curr_hp = fo::func::stat_level(target, fo::STAT_current_hp); - max_hp = fo::func::stat_level(target, fo::STAT_max_hit_points); - if (curr_hp < max_hp) return 0; + long max_hp = fo::func::stat_level(target, fo::STAT_max_hit_points); + if (target->critter.health < max_hp) return 0; fo::func::display_print(superStimMsg.c_str()); return -1; } -static const DWORD UseItemHookRet = 0x49C5F4; -static void __declspec(naked) SuperStimFix() { +static const DWORD protinst_use_item_on_Ret = 0x49C5F4; +static void __declspec(naked) protinst_use_item_on_hack() { __asm { push ecx; - mov ecx, ebx; // ecx - item - call SuperStimFix2; // edx - target + mov ecx, ebx; // ecx - item + call SuperStimFix; // edx - target pop ecx; test eax, eax; jnz end; - mov ebp, -1; // overwritten engine code + mov ebp, -1; // overwritten engine code retn; end: - add esp, 4; // destroy ret - jmp UseItemHookRet; // exit - } -} - -static int invenApCost, invenApCostDef; -static char invenApQPReduction; -void _stdcall SetInvenApCost(int cost) { - invenApCost = cost; -} -static const DWORD inven_ap_cost_hack_ret = 0x46E816; -static void __declspec(naked) inven_ap_cost_hack() { - _asm { - movzx ebx, byte ptr invenApQPReduction; - mul bl; - mov edx, invenApCost; - sub edx, eax; - mov eax, edx; - jmp inven_ap_cost_hack_ret; + add esp, 4; // destroy ret + jmp protinst_use_item_on_Ret; // exit } } @@ -570,7 +539,7 @@ DWORD __stdcall Inventory::adjust_fid_replacement() { using namespace fo; DWORD fid; - if (var::inven_dude->TypeFid() == fo::OBJ_TYPE_CRITTER) { + if (var::inven_dude->TypeFid() == ObjType::OBJ_TYPE_CRITTER) { DWORD frameNum; DWORD weaponAnimCode = 0; if (PartyControl::IsNpcControlled()) { @@ -652,6 +621,34 @@ static void __declspec(naked) do_move_timer_hook() { } } +static int invenApCost, invenApCostDef; +static char invenApQPReduction; +static const DWORD inven_ap_cost_Ret = 0x46E812; +static void __declspec(naked) inven_ap_cost_hack() { + _asm { + mul byte ptr invenApQPReduction; + mov edx, invenApCost; + jmp inven_ap_cost_Ret; + } +} + +static bool onlyOnceAP = false; +inline static void ApplyInvenApCostPatch() { + MakeJump(0x46E80B, inven_ap_cost_hack); + onlyOnceAP = true; +} + +void _stdcall SetInvenApCost(int cost) { + invenApCost = cost; + if (!onlyOnceAP) ApplyInvenApCostPatch(); +} + +// TODO: Make GetInvenApCost() function +/*long GetInvenApCost() { + long plevel = fo::func::perk_level(fo::var::obj_dude, fo::PERK_quick_pockets); + return invenApCost - (invenApQPReduction * plevel); +}*/ + void InventoryReset() { invenApCost = invenApCostDef; } @@ -712,17 +709,19 @@ void Inventory::init() { } } - invenApCost = invenApCostDef = GetConfigInt("Misc", "InventoryApCost", 4); - invenApQPReduction = GetConfigInt("Misc", "QuickPocketsApCostReduction", 2); - MakeJump(0x46E80B, inven_ap_cost_hack); - if (GetConfigInt("Misc", "SuperStimExploitFix", 0)) { superStimMsg = Translate("sfall", "SuperStimExploitMsg", "You cannot use a super stim on someone who is not injured!"); - MakeCall(0x49C3D9, SuperStimFix); + MakeCall(0x49C3D9, protinst_use_item_on_hack); } reloadWeaponKey = GetConfigInt("Input", "ReloadWeaponKey", 0); + invenApCost = invenApCostDef = GetConfigInt("Misc", "InventoryApCost", 4); + invenApQPReduction = GetConfigInt("Misc", "QuickPocketsApCostReduction", 2); + if (invenApCostDef != 4 || invenApQPReduction != 2) { + ApplyInvenApCostPatch(); + } + if (GetConfigInt("Misc", "StackEmptyWeapons", 0)) { MakeCall(0x4736C6, inven_action_cursor_hack); HookCall(0x4772AA, item_add_mult_hook); @@ -748,7 +747,7 @@ void Inventory::init() { BlockCall(0x4768A3); // mov ebx, 1 } - // Move items out of bag/backpack and back into the main inventory list by dragging them to character's image (similar to Fallout 1 behavior) + // Move items from bag/backpack to the main inventory list by dragging them on the character portrait (similar to Fallout 1 behavior) MakeCall(0x471452, inven_pickup_hack); // Move items to player's main inventory instead of the opened bag/backpack when confirming a trade diff --git a/sfall/Modules/KillCounter.cpp b/sfall/Modules/KillCounter.cpp index c460a1ace..75539914d 100644 --- a/sfall/Modules/KillCounter.cpp +++ b/sfall/Modules/KillCounter.cpp @@ -35,10 +35,10 @@ static const DWORD extraKillTypesCountAddr[] = { 0x4344E4, // Change char sheet to loop through the extra kill types }; -static int usingExtraKillTypes = 0; +static bool usingExtraKillTypes = false; bool UsingExtraKillTypes() { - return usingExtraKillTypes != 0; + return usingExtraKillTypes; } static DWORD __declspec(naked) ReadKillCounter() { @@ -57,7 +57,7 @@ static void __declspec(naked) IncKillCounter() { } static void KillCounterInit() { - usingExtraKillTypes = 1; + usingExtraKillTypes = true; // Overwrite the critter_kill_count_ function that reads the kill counter MakeCall(0x42D8B5, ReadKillCounter, 2); @@ -69,7 +69,7 @@ static void KillCounterInit() { // Edit the functions to accept kill types over 19 SafeWriteBatch(38, extraKillTypesCountAddr); - SafeWrite32(0x42D9DD, 1488); + SafeWrite32(0x42D9DD, 1488); // critter_kill_info_ } void KillCounter::init() { diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 4f65a04c2..374794d6c 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -38,6 +38,7 @@ static DWORD format; static bool cutsPatch = false; static std::vector patchFiles; +static std::vector savPrototypes; static void CheckPlayerGender() { isFemale = fo::HeroIsFemale(); @@ -185,6 +186,144 @@ static void GetExtraPatches() { } } +////////////////////////////// SAVE PARTY MEMBER PROTOTYPES ////////////////////////////// + +static void __fastcall AddSavPrototype(long pid) { + for (const auto& _pid : savPrototypes) { + if (_pid == pid) return; + } + savPrototypes.push_back(pid); +} + +static long ChangePrototypeExt(char* path) { + long len = strlen(path); + if (len) { + len -= 4; + if (path[len] == '.') { + path[++len] = 's'; + path[++len] = 'a'; + path[++len] = 'v'; + } else { + len = 0; + } + } + return len; +} + +static void __fastcall ExistSavPrototype(long pid, char* path) { + if (savPrototypes.empty()) return; + for (const auto& _pid : savPrototypes) { + if (_pid == pid) { + ChangePrototypeExt(path); + break; + } + } +} + +static long __fastcall CheckProtoType(long pid, char* path) { + if (pid >> 24 != fo::OBJ_TYPE_CRITTER) return 0; + return ChangePrototypeExt(path); +} + +// saves prototypes (all party members) before saving game or exiting the map +static void __declspec(naked) proto_save_pid_hook() { + __asm { + push ecx; + call fo::funcoffs::proto_list_str_; + test eax, eax; + jnz end; + mov ecx, ebx; // party pid + mov edx, edi; // path buffer + call CheckProtoType; + test eax, eax; + jz end; + mov ecx, ebx; // party pid + call AddSavPrototype; +end: + pop ecx; + retn; + } +} + +#define _F_PATHFILE 0x6143F4 +static void __declspec(naked) GameMap2Slot_hack() { // save party pids + __asm { + push ecx; + mov edx, _F_PATHFILE; // path buffer + call CheckProtoType; // ecx - party pid + pop ecx; + lea eax, [esp + 0x14 + 4]; + retn 0x14; + } +} + +static void __declspec(naked) SlotMap2Game_hack() { // load party pids + __asm { + push edx; + mov ecx, edi; // party pid + mov edx, _F_PATHFILE; // path buffer + call CheckProtoType; + test eax, eax; + jz end; + mov ecx, edi; + call AddSavPrototype; +end: + pop edx; + lea eax, [esp + 0x14 + 4]; + retn 0x14; + } +} + +static void __declspec(naked) proto_load_pid_hook() { +using namespace fo; + __asm { // eax - party pid + push ecx; + mov ecx, eax; + call fo::funcoffs::proto_list_str_; + test eax, eax; + jnz end; + // check pid type + mov edx, ecx; + shr edx, 24; + cmp edx, OBJ_TYPE_CRITTER; + jnz end; + mov edx, edi; // path buffer + call ExistSavPrototype; // ecx - party pid + xor eax, eax; +end: + pop ecx; + retn; + } +} + +static void ResetReadOnlyAttr() { + DWORD attr = GetFileAttributesA((const char*)_F_PATHFILE); + if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) { + SetFileAttributesA((const char*)_F_PATHFILE, (attr & ~FILE_ATTRIBUTE_READONLY)); + } +} + +static void __declspec(naked) SlotMap2Game_hack_attr() { +using namespace fo; + __asm { + cmp eax, -1; + je end; + cmp ebx, OBJ_TYPE_CRITTER; + jne end; + call ResetReadOnlyAttr; + or eax, 1; // reset ZF +end: + retn 0x8; + } +} + +#define _F_SAV (const char*)0x50A480 +#define _F_PROTO_CRITTERS (const char*)0x50A490 + +static void RemoveSavFiles() { + fo::func::MapDirErase(_F_PROTO_CRITTERS, _F_SAV); +} + void LoadOrder::init() { GetExtraPatches(); @@ -218,6 +357,21 @@ void LoadOrder::init() { } dlogr(" Done", DL_INIT); } + + dlog("Applying party member protos save/load patch.", DL_INIT); + savPrototypes.reserve(25); + HookCall(0x4A1CF2, proto_load_pid_hook); + HookCall(0x4A1BEE, proto_save_pid_hook); + MakeCall(0x47F5A5, GameMap2Slot_hack); // save game + MakeCall(0x47FB80, SlotMap2Game_hack); // load game + MakeCall(0x47FBBF, SlotMap2Game_hack_attr, 1); + dlogr(" Done", DL_INIT); + + LoadGameHook::OnAfterGameInit() += RemoveSavFiles; + LoadGameHook::OnGameReset() += []() { + savPrototypes.clear(); + RemoveSavFiles(); + }; } } diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index e1ebc99d9..93427e18a 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -59,50 +59,14 @@ static const DWORD walkDistanceAddr[] = { 0x411FF0, 0x4121C4, 0x412475, 0x412906, }; -static void __declspec(naked) Combat_p_procFix() { +static void __declspec(naked) apply_damage_hack() { __asm { - push eax; - - mov eax, dword ptr ds:[FO_VAR_combat_state]; - cmp eax, 3; - jnz end_cppf; - - push esi; - push ebx; - push edx; - - mov esi, FO_VAR_main_ctd; - mov eax, [esi]; - mov ebx, [esi + 0x20]; - xor edx, edx; - mov eax, [eax + 0x78]; - call fo::funcoffs::scr_set_objs_; - mov eax, [esi]; - - cmp dword ptr ds:[esi + 0x2c], +0x0; - jng jmp1; - - test byte ptr ds:[esi + 0x15], 0x1; - jz jmp1; - mov edx, 0x2; - jmp jmp2; -jmp1: - mov edx, 0x1; -jmp2: - mov eax, [eax + 0x78]; - call fo::funcoffs::scr_set_ext_param_; - mov eax, [esi]; - mov edx, 0xd; - mov eax, [eax + 0x78]; - call fo::funcoffs::exec_script_proc_; - pop edx; - pop ebx; - pop esi; - -end_cppf: - pop eax; - call fo::funcoffs::stat_level_; - + xor edx, edx; + inc edx; // COMBAT_SUBTYPE_WEAPON_USED + test [esi + 0x15], dl; // ctd.flags2Source & DAM_HIT_ + jz end; // no hit + inc edx; // COMBAT_SUBTYPE_HIT_SUCCEEDED +end: retn; } } @@ -110,9 +74,9 @@ static void __declspec(naked) Combat_p_procFix() { static void __declspec(naked) WeaponAnimHook() { __asm { cmp edx, 11; - je c11; + je c11; cmp edx, 15; - je c15; + je c15; jmp fo::funcoffs::art_get_code_; c11: mov edx, 16; @@ -279,60 +243,29 @@ static void __declspec(naked) CorpseHitFix2b() { } } -static DWORD RetryCombatLastAP; -static DWORD RetryCombatMinAP; -static void __declspec(naked) RetryCombatHook() { - using namespace fo; - using namespace Fields; - __asm { - mov RetryCombatLastAP, 0; -retry: - call fo::funcoffs::combat_ai_; -process: - cmp dword ptr ds:[FO_VAR_combat_turn_running], 0; - jle next; - call fo::funcoffs::process_bk_; - jmp process; -next: - 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; - } -} - static const DWORD NPCStage6Fix1End = 0x493D16; -static const DWORD NPCStage6Fix2End = 0x49423A; static void __declspec(naked) NPCStage6Fix1() { __asm { - mov eax,0xcc; // set record size to 204 bytes - imul eax,edx; // multiply by number of NPC records in party.txt - call fo::funcoffs::mem_malloc_; // malloc the necessary memory - mov edx,dword ptr ds:[FO_VAR_partyMemberMaxCount]; // retrieve number of NPC records in party.txt - mov ebx,0xcc; // set record size to 204 bytes - imul ebx,edx; // multiply by number of NPC records in party.txt - jmp NPCStage6Fix1End; // call memset to set all malloc'ed memory to 0 + mov eax, 204; // set record size to 204 bytes + imul eax, edx; // multiply by number of NPC records in party.txt + mov ebx, eax; // copy total record size for later memset + call fo::funcoffs::mem_malloc_; // malloc the necessary memory + jmp NPCStage6Fix1End; // call memset to set all malloc'ed memory to 0 } } +static const DWORD NPCStage6Fix2End = 0x49423A; static void __declspec(naked) NPCStage6Fix2() { __asm { - mov eax,0xcc; // record size is 204 bytes - imul edx,eax; // multiply by NPC number as listed in party.txt - mov eax,dword ptr ds:[FO_VAR_partyMemberAIOptions]; // get starting offset of internal NPC table - jmp NPCStage6Fix2End; // eax+edx = offset of specific NPC record + mov eax, 204; // record size is 204 bytes + imul edx, eax; // multiply by NPC number as listed in party.txt + mov eax, dword ptr ds:[FO_VAR_partyMemberAIOptions]; // get starting offset of internal NPC table + jmp NPCStage6Fix2End; // eax+edx = offset of specific NPC record } } -static const DWORD ScannerHookRet=0x41BC1D; -static const DWORD ScannerHookFail=0x41BC65; +static const DWORD ScannerHookRet = 0x41BC1D; +static const DWORD ScannerHookFail = 0x41BC65; static void __declspec(naked) ScannerAutomapHook() { using fo::PID_MOTION_SENSOR; __asm { @@ -609,10 +542,9 @@ void DontTurnOffSneakIfYouRunPatch() { void CombatProcFix() { //Ray's combat_p_proc fix - dlog("Applying combat_p_proc fix.", DL_INIT); - HookCall(0x425252, Combat_p_procFix); - SafeWrite8(0x424DBC, 0xE9); - SafeWrite32(0x424DBD, 0x00000034); + dlog("Applying Ray's combat_p_proc patch.", DL_INIT); + MakeCall(0x424DD9, apply_damage_hack); + SafeWrite8(0x424DC7, 0x0); dlogr(" Done", DL_INIT); } @@ -642,21 +574,12 @@ void CorpseLineOfFireFix() { } } -void ApplyNpcExtraApPatch() { - RetryCombatMinAP = GetConfigInt("Misc", "NPCsTryToSpendExtraAP", 0); - if (RetryCombatMinAP > 0) { - dlog("Applying retry combat patch.", DL_INIT); - HookCall(0x422B94, RetryCombatHook); // combat_turn_ - dlogr(" Done", DL_INIT); - } -} - void NpcStage6Fix() { if (GetConfigInt("Misc", "NPCStage6Fix", 0)) { dlog("Applying NPC Stage 6 Fix.", DL_INIT); MakeJump(0x493CE9, NPCStage6Fix1); - SafeWrite8(0x494063, 0x06); // loop should look for a potential 6th stage - SafeWrite8(0x4940BB, 0xCC); // move pointer by 204 bytes instead of 200 + SafeWrite8(0x494063, 6); // loop should look for a potential 6th stage + SafeWrite8(0x4940BB, 204); // move pointer by 204 bytes instead of 200 MakeJump(0x494224, NPCStage6Fix2); dlogr(" Done", DL_INIT); } @@ -933,6 +856,9 @@ void MiscPatches::init() { dlogr(" Done", DL_INIT); } + // Increase the max text width of the information card in the character screen + SafeWriteBatch(144, {0x43ACD5, 0x43DD37}); // 136, 133 + LoadGameHook::OnBeforeGameStart() += BodypartHitChances; // set on start & load CombatProcFix(); @@ -943,8 +869,6 @@ void MiscPatches::init() { PlayIdleAnimOnReloadPatch(); CorpseLineOfFireFix(); - ApplyNpcExtraApPatch(); - SkilldexImagesPatch(); SpeedInterfaceCounterAnimsPatch(); diff --git a/sfall/Modules/Movies.cpp b/sfall/Modules/Movies.cpp index 13bb3a40c..d1e9ad7b1 100644 --- a/sfall/Modules/Movies.cpp +++ b/sfall/Modules/Movies.cpp @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -#include // should be above DX SDK includes to avoid warning 4995 +//#include // should be above DX SDK includes to avoid warning 4995 #include #include @@ -37,11 +37,6 @@ namespace sfall static DWORD MoviePtrs[MaxMovies]; char MoviePaths[MaxMovies * 65]; -extern IDirect3D9* d3d9; -extern IDirect3DDevice9* d3d9Device; - -#define SAFERELEASE(a) { if (a) { a->Release(); a = 0; } } - class CAllocator : public IVMRSurfaceAllocator9, IVMRImagePresenter9 { private: IDirect3DSurface9* surface; diff --git a/sfall/Modules/Objects.cpp b/sfall/Modules/Objects.cpp index 7d54e5b2d..7f73f6d58 100644 --- a/sfall/Modules/Objects.cpp +++ b/sfall/Modules/Objects.cpp @@ -26,7 +26,7 @@ namespace sfall { static int unjamTimeState; -static int maxCountProto = 512; +static int maxCountLoadProto = 512; long Objects::uniqueID = UniqueID::Start; // current counter id, saving to sfallgv.sav @@ -35,7 +35,7 @@ long Objects::uniqueID = UniqueID::Start; // current counter id, saving to sfall // player ID = 18000, all party members have ID = 18000 + its pid (file number of prototype) long Objects::SetObjectUniqueID(fo::GameObject* obj) { 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 (id > UniqueID::Start || obj == fo::var::obj_dude || (id >= PLAYER_ID && id < 83536)) return id; // 65535 maximum possible number of prototypes if ((DWORD)uniqueID >= UniqueID::End) uniqueID = UniqueID::Start; obj->id = ++uniqueID; @@ -112,13 +112,13 @@ void RestoreObjUnjamAllLocks() { static void __declspec(naked) proto_ptr_hack() { __asm { - mov ecx, maxCountProto; + mov ecx, maxCountLoadProto; cmp ecx, 4096; jae skip; cmp eax, ecx; jb end; add ecx, 256; - mov maxCountProto, ecx; + mov maxCountLoadProto, ecx; skip: cmp eax, ecx; end: @@ -127,9 +127,7 @@ static void __declspec(naked) proto_ptr_hack() { } void Objects::LoadProtoAutoMaxLimit() { - if (maxCountProto != -1) { - MakeCall(0x4A21B2, proto_ptr_hack); - } + MakeCall(0x4A21B2, proto_ptr_hack); } void Objects::init() { @@ -137,15 +135,6 @@ void Objects::init() { RestoreObjUnjamAllLocks(); }; - int maxlimit = GetConfigInt("Misc", "LoadProtoMaxLimit", -1); - if (maxlimit != -1) { - maxCountProto = -1; - if (maxlimit > 512) { - if (maxlimit > 4096) maxlimit = 4096; - SafeWrite32(0x4A21B3, maxlimit); - } - } - HookCall(0x4A38A5, new_obj_id_hook); SafeWrite8(0x4A38B3, 0x90); // fix ID increment diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 2f08ee70e..6740d5ba8 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -131,22 +131,27 @@ static void SetCurrentDude(fo::GameObject* npc) { // reset traits fo::var::pc_trait[0] = fo::var::pc_trait[1] = -1; - // reset perks - for (int i = 0; i < fo::PERK_count; i++) { - fo::var::perkLevelDataList[i] = 0; + // copy existing party member perks or reset list for non-party member NPC + long isPartyMember = fo::IsPartyMemberByPid(npc->protoId); + if (isPartyMember) { + memcpy(fo::var::perkLevelDataList, fo::var::perkLevelDataList + (fo::PERK_count * (isPartyMember - 1)), sizeof(DWORD) * fo::PERK_count); + } else { + for (int i = 0; i < fo::PERK_count; i++) { + fo::var::perkLevelDataList[i] = 0; + } } - // change character name - fo::func::critter_pc_set_name(fo::func::critter_name(npc)); - // change level - int level = fo::func::isPartyMember(npc) - ? fo::func::partyMemberGetCurLevel(npc) - : 0; + int level = (isPartyMember) // fo::func::isPartyMember(npc) + ? fo::func::partyMemberGetCurLevel(npc) + : 0; fo::var::Level_ = level; fo::var::last_level = level; + // change character name + fo::func::critter_pc_set_name(fo::func::critter_name(npc)); + // reset other stats fo::var::Experience_ = 0; fo::var::free_perk = 0; @@ -155,10 +160,10 @@ static void SetCurrentDude(fo::GameObject* npc) { // deduce active hand by weapon anim code char critterAnim = (npc->artFid & 0xF000) >> 12; // current weapon as seen in hands - if (fo::AnimCodeByWeapon(fo::func::inven_left_hand(npc)) == critterAnim) { // definitely left hand.. - fo::var::itemCurrentItem = 0; + if (fo::AnimCodeByWeapon(fo::func::inven_left_hand(npc)) == critterAnim) { // definitely left hand + fo::var::itemCurrentItem = fo::ActiveSlot::Left; } else { - fo::var::itemCurrentItem = 1; + fo::var::itemCurrentItem = fo::ActiveSlot::Right; } bool isAddict = false; @@ -232,7 +237,7 @@ int __stdcall PartyControl::SwitchHandHook(fo::GameObject* item) { if (isControllingNPC && fo::func::item_get_type(item) == fo::ItemType::item_type_weapon) { int fId = fo::var::obj_dude->artFid; long weaponCode = fo::AnimCodeByWeapon(item); - fId = (fId & 0xffff0fff) | (weaponCode << 12); + fId = (fId & 0xFFFF0FFF) | (weaponCode << 12); if (!fo::func::art_exists(fId)) { DisplayCantDoThat(); return 1; @@ -241,28 +246,41 @@ int __stdcall PartyControl::SwitchHandHook(fo::GameObject* item) { return -1; } +long __fastcall GetRealDudePerk(fo::GameObject* source, long perk) { + if (isControllingNPC && source == realDude.obj_dude) { + return realDude.perkLevelDataList[perk]; + } + return fo::func::perk_level(source, perk); +} + +long __fastcall GetRealDudeTrait(fo::GameObject* source, long trait) { + if (isControllingNPC && source == realDude.obj_dude) { + return (trait == realDude.traits[0] || trait == realDude.traits[1]) ? 1 : 0; + } + return fo::func::trait_level(trait); +} + static void __declspec(naked) stat_pc_add_experience_hook() { __asm { - cmp isControllingNPC, 0 - je skip - add delayedExperience, esi - retn + cmp isControllingNPC, 0; + je skip; + add delayedExperience, esi; + retn; skip: - xchg esi, eax - jmp fo::funcoffs::stat_pc_add_experience_ + xchg esi, eax; + jmp fo::funcoffs::stat_pc_add_experience_; } } // prevents using sneak when controlling NPCs static void __declspec(naked) pc_flag_toggle_hook() { __asm { - cmp isControllingNPC, 0 - je end - call DisplayCantDoThat - retn + cmp isControllingNPC, 0; + je end; + call DisplayCantDoThat; + retn; end: - call fo::funcoffs::pc_flag_toggle_ - retn + jmp fo::funcoffs::pc_flag_toggle_; } } @@ -278,6 +296,19 @@ bool PartyControl::IsNpcControlled() { void PartyControl::SwitchToCritter(fo::GameObject* critter) { if (isControllingNPC) { + if (fo::var::itemCurrentItem == fo::ActiveSlot::Left) { + // set active left item to right slot + fo::GameObject* lItem = fo::func::inven_left_hand(fo::var::obj_dude); + if (lItem) { + fo::GameObject* rItem = fo::func::inven_right_hand(fo::var::obj_dude); + lItem->flags &= ~fo::ObjectFlag::Left_Hand; + lItem->flags |= fo::ObjectFlag::Right_Hand; + if (rItem) { + rItem->flags &= ~fo::ObjectFlag::Right_Hand; + rItem->flags |= fo::ObjectFlag::Left_Hand; + } + } + } if (critter == nullptr || critter == realDude.obj_dude) RestoreRealDudeState(); } else { SaveRealDudeState(); @@ -287,6 +318,10 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) { if (switchHandHookInjected) return; switchHandHookInjected = true; if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); + // Gets dude perks and traits from script while controlling another NPC + // WARNING: Handling dude perks/traits in the engine code while controlling another NPC remains impossible, this requires serious hacking of the engine code + HookCall(0x458242, GetRealDudePerk); // op_has_trait_ + HookCall(0x458326, GetRealDudeTrait); // op_has_trait_ } } diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index 1fe06ea4c..738ab4986 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -21,6 +21,7 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "LoadGameHook.h" +#include "PartyControl.h" #include "Perks.h" @@ -75,12 +76,13 @@ struct FakePerk { 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 + char reserve[506]; // empty block + int ownerId; // 0 = the player, or ID number of party member NPC + short id; // perk id (use last bytes of the description under the ID value for compatibility) FakePerk() {} - FakePerk(char* _name, int _level, int _image, char* _desc, short _id = -1) : id(_id), Name {0}, Desc {0}, reserve {0} { + FakePerk(const char* _name, int _level, int _image, const char* _desc, int npcId, short _id = -1) : ownerId(npcId), id(_id), Name {0}, Desc {0}, reserve {0} { Level = _level; Image = _image; strncpy_s(this->Name, _name, _TRUNCATE); @@ -151,7 +153,7 @@ static long _stdcall LevelUp() { } } - int level = fo::var::Level_; // Get players level + int level = fo::var::Level_; // Get player's level if (!((level + 1) % eachLevel)) fo::var::free_perk++; // Increment the number of perks owed return level; } @@ -191,11 +193,21 @@ void _stdcall SetPerkboxTitle(char* name) { } } -void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { - if (active < 0 || active > 1) return; +static bool IsOwnedFake(int ownerId) { + return ((!ownerId && !PartyControl::IsNpcControlled()) || ownerId == fo::var::obj_dude->id); +} + +static bool IsNotOwnedFake(int ownerId1, int ownerId2) { + return (ownerId1 != ownerId2); +} + +void Perks::SetSelectablePerk(const char* name, int active, int image, const char* desc, int npcID) { + if (active < 0) return; + if (active > 1) active = 1; size_t size = fakeSelectablePerks.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeSelectablePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks.erase(fakeSelectablePerks.begin() + i); return; @@ -203,6 +215,7 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } } else { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeSelectablePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakeSelectablePerks[i].Name)) { fakeSelectablePerks[i].Level = active; fakeSelectablePerks[i].Image = image; @@ -212,15 +225,17 @@ void _stdcall SetSelectablePerk(char* name, int active, int image, char* desc) { } } if (size == fakeSelectablePerks.capacity()) fakeSelectablePerks.reserve(size + 10); - fakeSelectablePerks.emplace_back(name, active, image, desc); + fakeSelectablePerks.emplace_back(name, active, image, desc, npcID); } } -void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { - if (level < 0 || level > 100) return; +void Perks::SetFakePerk(const char* name, int level, int image, const char* desc, int npcID) { + if (level < 0) return; + if (level > 100) level = 100; size_t size = fakePerks.size(); if (level == 0) { // remove perk from fakePerks for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakePerks[i].Name)) { fakePerks.erase(fakePerks.begin() + i); return; @@ -228,6 +243,7 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } } else { // add or change the existing fake perk in fakePerks for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, npcID)) continue; if (!strcmp(name, fakePerks[i].Name)) { fakePerks[i].Level = level; fakePerks[i].Image = image; @@ -237,15 +253,17 @@ void _stdcall SetFakePerk(char* name, int level, int image, char* desc) { } } if (size == fakePerks.capacity()) fakePerks.reserve(size + 10); - fakePerks.emplace_back(name, level, image, desc); + fakePerks.emplace_back(name, level, image, desc, npcID); } } -void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { - if (active < 0 || active > 1) return; +void Perks::SetFakeTrait(const char* name, int active, int image, const char* desc, int npcID) { + if (active < 0) return; + if (active > 1) active = 1; size_t size = fakeTraits.size(); if (active == 0) { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, npcID)) continue; if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits.erase(fakeTraits.begin() + i); return; @@ -253,6 +271,7 @@ void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { } } else { for (size_t i = 0; i < size; i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, npcID)) continue; if (!strcmp(name, fakeTraits[i].Name)) { fakeTraits[i].Level = active; fakeTraits[i].Image = image; @@ -262,7 +281,7 @@ void _stdcall SetFakeTrait(char* name, int active, int image, char* desc) { } } if (size == fakeTraits.capacity()) fakeTraits.reserve(size + 5); - fakeTraits.emplace_back(name, active, image, desc); + fakeTraits.emplace_back(name, active, image, desc, npcID); } } @@ -271,7 +290,8 @@ static DWORD _stdcall HaveFakePerks() { } static long __fastcall GetFakePerkLevel(int id) { - return fakePerks[id - PERK_count].Level; + int i = id - PERK_count; + return IsOwnedFake(fakePerks[i].ownerId) ? fakePerks[i].Level : 0; } static long __fastcall GetFakePerkImage(int id) { @@ -282,16 +302,24 @@ static FakePerk* __fastcall GetFakePerk(int id) { return &fakePerks[id - PERK_count]; } +// Get level of taken perk 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; + if (!PartyControl::IsNpcControlled()) { // extra perks are not available for controlled NPC + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (fakePerks[i].id == id) return fakePerks[i].Level; + } } return 0; } - char* name = fakeSelectablePerks[id - startFakeID].Name; + long n = id - startFakeID; + const char* name = fakeSelectablePerks[n].Name; + int ownerID = fakeSelectablePerks[n].ownerId; for (DWORD i = 0; i < fakePerks.size(); i++) { - if (!strcmp(name, fakePerks[i].Name)) return fakePerks[i].Level; + if (ownerID != fakePerks[i].ownerId) continue; + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; + } } return 0; } @@ -313,8 +341,10 @@ static FakePerk* __fastcall GetFakeSelectPerk(int id) { return &fakeSelectablePerks[id - startFakeID]; } +// Print a list of fake traits static DWORD HandleFakeTraits(int isSelect) { for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (!IsOwnedFake(fakeTraits[i].ownerId)) continue; if (fo::func::folder_print_line(fakeTraits[i].Name) && !isSelect) { isSelect = 1; var::folder_card_fid = fakeTraits[i].Image; @@ -326,17 +356,19 @@ static DWORD HandleFakeTraits(int isSelect) { return isSelect; } -static long __fastcall PlayerHasPerk(int* isSelectPtr) { - *isSelectPtr = HandleFakeTraits(*isSelectPtr); +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 0x43438A; // print perks } - return (!fakePerks.empty()) ? 0x43438A : 0x434446; // skip print perks + return (!fakePerks.empty()) + ? 0x43438A // print perks + : 0x434446; // skip print perks } -static DWORD __fastcall HaveFakeTraits(int* isSelectPtr) { - return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; +static DWORD __fastcall HaveFakeTraits(int &isSelectPtr) { + return (fakeTraits.empty()) ? PlayerHasPerk(isSelectPtr) : 0x43425B; // print traits } static void __declspec(naked) PlayerHasPerkHack() { @@ -344,7 +376,7 @@ static void __declspec(naked) PlayerHasPerkHack() { push ecx; // isSelect mov ecx, esp; // ptr to isSelect call PlayerHasPerk; - pop ecx; // value from HandleFakeTraits + pop ecx; // isSelect value from HandleFakeTraits jmp eax; } } @@ -354,7 +386,7 @@ static void __declspec(naked) PlayerHasTraitHook() { push ecx; // isSelect mov ecx, esp; // ptr to isSelect call HaveFakeTraits; - pop ecx; // value from HandleFakeTraits + pop ecx; // isSelect value from HandleFakeTraits jmp eax; } } @@ -435,19 +467,23 @@ static void __declspec(naked) EndPerkLoopHack() { } } -// Build a table of perks ID numbers available for selection -// data buffer has limited size for 119 perks +// 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) return available; // exit if the buffer is overfull - if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; + size_t count; + if (!PartyControl::IsNpcControlled()) { // extra perks are not available for controlled NPC + count = extPerks.size(); + for (size_t i = 0; i < count; i++) { + if (available >= 119) return available; // exit if the buffer is overfull + if (fo::func::perk_can_add(fo::var::obj_dude, extPerks[i].id)) data[available++] = extPerks[i].id; + } } 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); + if (IsOwnedFake(fakeSelectablePerks[i].ownerId)) { + if (available >= 119) break; + // for fake perks, their ID should start from 256 + data[available++] = startFakeID + i; + } } return available; // total number of perks available for selection } @@ -456,8 +492,7 @@ static void __declspec(naked) GetAvailablePerksHook() { __asm { push ecx; push edx; // arg data - mov ecx, IgnoringDefaultPerks; - test ecx, ecx; + cmp IgnoringDefaultPerks, 0; jnz skipDefaults; call fo::funcoffs::perk_make_list_; // return available count jmp next; @@ -573,10 +608,10 @@ static void ApplyPerkEffect(long index, fo::GameObject* critter, long type) { } // Adds the selected perk to the player -static void _stdcall AddFakePerk(DWORD perkID) { +static long _stdcall AddFakePerk(DWORD perkID) { size_t count; bool matched = false; - if (perkID < startFakeID) { + if (perkID < startFakeID) { // extra perk can only be added to the player count = fakePerks.size(); for (size_t d = 0; d < count; d++) { if (fakePerks[d].id == perkID) { @@ -587,18 +622,20 @@ static void _stdcall AddFakePerk(DWORD perkID) { } } if (!matched) { // add to fakePerks - RemovePerkID.push_back(count); // index of the added perk int index = PerkSearchID(perkID); - fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, extPerks[index].id); // id same as perkID + if (index < 0) return -1; + RemovePerkID.push_back(count); // index of the added perk + fakePerks.emplace_back(extPerks[index].Name, 1, extPerks[index].data.image, extPerks[index].Desc, 0, extPerks[index].id); // id same as perkID } fo::func::perk_add_effect(fo::var::obj_dude, perkID); - return; + return 0; } // 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 (IsNotOwnedFake(fakeTraits[d].ownerId, fakeSelectablePerks[perkID].ownerId)) continue; if (!strcmp(fakeTraits[d].Name, fakeSelectablePerks[perkID].Name)) { matched = true; break; @@ -613,6 +650,7 @@ static void _stdcall AddFakePerk(DWORD perkID) { matched = false; count = fakePerks.size(); for (size_t d = 0; d < count; d++) { + if (IsNotOwnedFake(fakePerks[d].ownerId, fakeSelectablePerks[perkID].ownerId)) continue; if (!strcmp(fakePerks[d].Name, fakeSelectablePerks[perkID].Name)) { RemovePerkID.push_back(d); fakePerks[d].Level++; @@ -626,9 +664,9 @@ static void _stdcall AddFakePerk(DWORD perkID) { } } if (addPerkMode & 4) { // delete from selectable perks - RemoveSelectableID.push_back(perkID); - //fakeSelectablePerks.remove_at(perkID); + RemoveSelectableID.push_back(perkID); //fakeSelectablePerks.remove_at(perkID); } + return 0; } // Adds perk from selection window to player @@ -640,7 +678,6 @@ static void __declspec(naked) AddPerkHook() { push edx; call AddFakePerk; pop ecx; - xor eax, eax; retn; normalPerk: push edx; @@ -1282,23 +1319,43 @@ void _stdcall AddPerkMode(DWORD mode) { addPerkMode = mode; } -DWORD HasFakePerk(const char* name, long id) { - if (id < PERK_count && name[0] == 0) return 0; +DWORD Perks::HasFakePerk(const char* name, long perkId) { + if ((perkId < PERK_count && name[0] == 0) || (perkId && PartyControl::IsNpcControlled())) return 0; for (DWORD i = 0; i < fakePerks.size(); i++) { - 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 - } + if (perkId) { + if (fakePerks[i].id == perkId) return fakePerks[i].Level; // current perk level + } else if (IsOwnedFake(fakePerks[i].ownerId) && !strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; + } + } + return 0; +} + +DWORD Perks::HasFakeTrait(const char* name) { + if (name[0] == 0) return 0; + for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (IsOwnedFake(fakeTraits[i].ownerId) && !strcmp(name, fakeTraits[i].Name)) { + return 1; + } + } + return 0; +} + +DWORD Perks::HasFakePerkOwner(const char* name, long objId) { + if (name[0] == 0) return 0; + for (DWORD i = 0; i < fakePerks.size(); i++) { + if (IsNotOwnedFake(fakePerks[i].ownerId, objId)) continue; + if (!strcmp(name, fakePerks[i].Name)) { + return fakePerks[i].Level; } } return 0; } -DWORD _stdcall HasFakeTrait(const char* name) { +DWORD Perks::HasFakeTraitOwner(const char* name, long objId) { if (name[0] == 0) return 0; for (DWORD i = 0; i < fakeTraits.size(); i++) { + if (IsNotOwnedFake(fakeTraits[i].ownerId, objId)) continue; if (!strcmp(name, fakeTraits[i].Name)) { return 1; } diff --git a/sfall/Modules/Perks.h b/sfall/Modules/Perks.h index 4af5f7f8d..5b4482863 100644 --- a/sfall/Modules/Perks.h +++ b/sfall/Modules/Perks.h @@ -30,6 +30,15 @@ class Perks : public Module { static void save(HANDLE file); static bool load(HANDLE file); + + static void SetSelectablePerk(const char* name, int active, int image, const char* desc, int npcID = 0); + static void SetFakePerk(const char* name, int level, int image, const char* desc, int npcID = 0); + static void SetFakeTrait(const char* name, int active, int image, const char* desc, int npcID = 0); + + static DWORD HasFakePerk(const char* name, long perkId); + static DWORD HasFakeTrait(const char* name); + static DWORD HasFakePerkOwner(const char* name, long objId); + static DWORD HasFakeTraitOwner(const char* name, long objId); }; void PerksEnterCharScreen(); @@ -43,17 +52,12 @@ void _stdcall SetPerkName(int id, char* value); void _stdcall SetPerkDesc(int id, char* value); void _stdcall SetPerkboxTitle(char* title); -void _stdcall SetSelectablePerk(char* name, int level, int image, char* desc); -void _stdcall SetFakePerk(char* name, int level, int image, char* desc); -void _stdcall SetFakeTrait(char* name, int level, int image, char* desc); + void _stdcall IgnoreDefaultPerks(); void _stdcall RestoreDefaultPerks(); - void _stdcall AddPerkMode(DWORD mode); -DWORD HasFakePerk(const char* name, long id); -DWORD _stdcall HasFakeTrait(const char* name); -void _stdcall ClearSelectablePerks(); +void _stdcall ClearSelectablePerks(); void _stdcall SetPerkFreq(int i); } diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 833abd510..357faaec2 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -17,7 +17,7 @@ */ #include -#include +//#include #include #include #include @@ -34,9 +34,11 @@ #include "LoadGameHook.h" #include "MainLoopHook.h" #include "Worldmap.h" + #include "Scripting\Arrays.h" #include "Scripting\Opcodes.h" #include "Scripting\OpcodeContext.h" +#include "Scripting\Handlers\Anims.h" #include "ScriptExtender.h" @@ -76,6 +78,8 @@ struct SelfOverrideObj { fo::GameObject* object; char counter; + SelfOverrideObj(fo::GameObject* obj) : object(obj), counter(0) {} + bool UnSetSelf() { if (counter) counter--; return counter == 0; @@ -102,7 +106,7 @@ DWORD isGlobalScriptLoading = 0; std::unordered_map<__int64, int> globalVars; typedef std::unordered_map<__int64, int> :: iterator glob_itr; typedef std::unordered_map<__int64, int> :: const_iterator glob_citr; -typedef std::pair<__int64, int> glob_pair; +//typedef std::pair<__int64, int> glob_pair; DWORD availableGlobalScriptTypes = 0; bool isGameLoading; @@ -341,7 +345,7 @@ void __fastcall SetGlobalScriptType(fo::Program* script, int type) { static void SetGlobalVarInternal(__int64 var, int val) { glob_itr itr = globalVars.find(var); if (itr == globalVars.end()) { - globalVars.insert(glob_pair(var, val)); + globalVars.emplace(var, val); } else { if (val == 0) { globalVars.erase(itr); // applies for both float 0.0 and integer 0 @@ -395,10 +399,10 @@ void __fastcall SetSelfObject(fo::Program* script, fo::GameObject* obj) { it->second.counter = 0; } } else { - selfOverrideMap[script] = {obj, 0}; + selfOverrideMap.emplace(script, obj); } - } else { - if (isFind) selfOverrideMap.erase(it); + } else if (isFind) { + selfOverrideMap.erase(it); } } @@ -525,21 +529,6 @@ bool _stdcall ScriptHasLoaded(fo::Program* script) { return true; } -void _stdcall RegAnimCombatCheck(DWORD newValue) { - char oldValue = regAnimCombatCheck; - regAnimCombatCheck = (newValue > 0); - if (oldValue != regAnimCombatCheck) { - SafeWrite8(0x459C97, regAnimCombatCheck); // reg_anim_func - SafeWrite8(0x459D4B, regAnimCombatCheck); // reg_anim_animate - SafeWrite8(0x459E3B, regAnimCombatCheck); // reg_anim_animate_reverse - SafeWrite8(0x459EEB, regAnimCombatCheck); // reg_anim_obj_move_to_obj - SafeWrite8(0x459F9F, regAnimCombatCheck); // reg_anim_obj_run_to_obj - SafeWrite8(0x45A053, regAnimCombatCheck); // reg_anim_obj_move_to_tile - SafeWrite8(0x45A10B, regAnimCombatCheck); // reg_anim_obj_run_to_tile - SafeWrite8(0x45AE53, regAnimCombatCheck); // reg_anim_animate_forever - } -} - // this runs before actually loading/starting the game static void ClearGlobalScripts() { isGameLoading = true; @@ -580,11 +569,7 @@ static void RunScript(GlobalScript* script) { - reset reg_anim_* combatstate checks */ static void ResetStateAfterFrame() { - if (!tempArrays.empty()) { - for (std::set::iterator it = tempArrays.begin(); it != tempArrays.end(); ++it) - FreeArray(*it); - tempArrays.clear(); - } + DeleteAllTempArrays(); RegAnimCombatCheck(1); } @@ -646,7 +631,7 @@ bool LoadGlobals(HANDLE h) { GlobalVar var; for (DWORD i = 0; i < count; i++) { ReadFile(h, &var, sizeof(GlobalVar), &unused, 0); - globalVars.insert(glob_pair(var.id, var.val)); + globalVars.emplace(var.id, var.val); } return false; } @@ -697,6 +682,17 @@ void SetGlobals(GlobalVar* globals) { } } +static void __declspec(naked) map_save_in_game_hook() { + __asm { + call fo::funcoffs::partyMemberSaveProtos_; + test cl, 1; + jz skip; + call fo::funcoffs::queue_leaving_map_; +skip: + jmp fo::funcoffs::game_time_; + } +} + void ScriptExtender::init() { LoadGameHook::OnAfterGameStarted() += LoadGlobalScripts; LoadGameHook::OnGameReset() += [] () { @@ -751,6 +747,12 @@ void ScriptExtender::init() { HookCall(0x421B72, CombatBeginHook); HookCall(0x421FC1, CombatOverHook); + // Reorder the execution of functions before exiting the map + // Call saving party member prototypes and removing the drug effects for NPC after executing map_exit_p_proc procedure + HookCall(0x483CF9, map_save_in_game_hook); + BlockCall(0x483CB4); // partyMemberSaveProtos_ + BlockCall(0x483CBE); // queue_leaving_map_ + InitNewOpcodes(); } diff --git a/sfall/Modules/ScriptExtender.h b/sfall/Modules/ScriptExtender.h index 295fb9ab8..bde1fb274 100644 --- a/sfall/Modules/ScriptExtender.h +++ b/sfall/Modules/ScriptExtender.h @@ -36,12 +36,13 @@ class ScriptExtender : public Module { static Delegate<>& OnMapExit(); }; -#pragma pack(8) +#pragma pack(push, 8) struct GlobalVar { __int64 id; __int32 val; __int32 unused; }; +#pragma pack(pop) typedef struct { fo::Program* ptr = nullptr; @@ -70,8 +71,6 @@ long GetGlobalVarInternal(__int64 val); void __fastcall SetSelfObject(fo::Program* script, fo::GameObject* obj); -void _stdcall RegAnimCombatCheck(DWORD newValue); - bool _stdcall ScriptHasLoaded(fo::Program* script); // loads script from .int file into a sScriptProgram struct, filling script pointer and proc lookup table @@ -93,7 +92,6 @@ void AddProgramToMap(ScriptProgram &prog); ScriptProgram* GetGlobalScriptProgram(fo::Program* scriptPtr); // variables -static char regAnimCombatCheck = 1; extern DWORD isGlobalScriptLoading; extern DWORD availableGlobalScriptTypes; extern bool alwaysFindScripts; diff --git a/sfall/Modules/ScriptShaders.cpp b/sfall/Modules/ScriptShaders.cpp new file mode 100644 index 000000000..37f58554c --- /dev/null +++ b/sfall/Modules/ScriptShaders.cpp @@ -0,0 +1,191 @@ +/* + * 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 "Graphics.h" +#include "LoadGameHook.h" + +#include "ScriptShaders.h" + +namespace sfall +{ + +static size_t shadersSize; + +struct sShader { + ID3DXEffect* Effect; + D3DXHANDLE ehTicks; + DWORD mode; + DWORD mode2; + bool Active; + + sShader() : Effect(0), ehTicks(0), mode(0), mode2(0), Active(false) {} +}; + +static std::vector shaders; +static std::vector shaderTextures; + +size_t ScriptShaders::Count() { + return shadersSize; +} + +void _stdcall SetShaderMode(DWORD d, DWORD mode) { + if (d >= shadersSize || !shaders[d].Effect) return; + if (mode & 0x80000000) { + shaders[d].mode2 = mode ^ 0x80000000; + } else { + shaders[d].mode = mode; + } +} + +int _stdcall LoadShader(const char* path) { + if (!Graphics::mode || strstr(path, "..") || strstr(path, ":")) return -1; + char buf[MAX_PATH]; + sprintf_s(buf, "%s\\shaders\\%s", fo::var::patches, path); + for (DWORD d = 0; d < shadersSize; d++) { + if (!shaders[d].Effect) { + if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shaders[d].Effect, 0))) return -1; + else return d; + } + } + sShader shader = sShader(); + if (FAILED(D3DXCreateEffectFromFile(d3d9Device, buf, 0, 0, 0, 0, &shader.Effect, 0))) return -1; + + shader.Effect->SetFloatArray("rcpres", Graphics::rcpresGet(), 2); + + for (int i = 1; i < 128; i++) { + const char* name; + IDirect3DTexture9* tex; + + sprintf(buf, "texname%d", i); + if (FAILED(shader.Effect->GetString(buf, &name))) break; + sprintf_s(buf, "%s\\art\\stex\\%s", fo::var::patches, name); + if (FAILED(D3DXCreateTextureFromFileA(d3d9Device, buf, &tex))) continue; + sprintf(buf, "tex%d", i); + shader.Effect->SetTexture(buf, tex); + shaderTextures.push_back(tex); + } + + shader.ehTicks = shader.Effect->GetParameterByName(0, "tickcount"); + shaders.push_back(shader); + shadersSize = shaders.size(); + return shadersSize - 1; +} + +void _stdcall ActivateShader(DWORD d) { + if (d < shadersSize && shaders[d].Effect) shaders[d].Active = true; +} + +void _stdcall DeactivateShader(DWORD d) { + if (d < shadersSize) shaders[d].Active = false; +} + +int _stdcall GetShaderTexture(DWORD d, DWORD id) { + if (id < 1 || id > 128 || d >= shadersSize || !shaders[d].Effect) return -1; + IDirect3DBaseTexture9* tex = 0; + char buf[8] = "tex"; + _itoa_s(id, &buf[3], 4, 10); + if (FAILED(shaders[d].Effect->GetTexture(buf, &tex)) || !tex) return -1; + tex->Release(); + for (DWORD i = 0; i < shaderTextures.size(); i++) { + if (shaderTextures[i] == tex) return i; + } + return -1; +} + +void _stdcall FreeShader(DWORD d) { + if (d < shadersSize) { + SAFERELEASE(shaders[d].Effect); + shaders[d].Active = false; + } +} + +void _stdcall SetShaderInt(DWORD d, const char* param, int value) { + if (d >= shadersSize || !shaders[d].Effect) return; + shaders[d].Effect->SetInt(param, value); +} + +void _stdcall SetShaderFloat(DWORD d, const char* param, float value) { + if (d >= shadersSize || !shaders[d].Effect) return; + shaders[d].Effect->SetFloat(param, value); +} + +void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4) { + if (d >= shadersSize || !shaders[d].Effect) return; + shaders[d].Effect->SetFloatArray(param, &f1, 4); +} + +void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value) { + if (d >= shadersSize || !shaders[d].Effect || value >= shaderTextures.size()) return; + shaders[d].Effect->SetTexture(param, shaderTextures[value]); +} + +void ResetShaders() { + for (DWORD d = 0; d < shadersSize; d++) SAFERELEASE(shaders[d].Effect); + shaders.clear(); + shadersSize = 0; +} + +void ScriptShaders::Refresh(IDirect3DSurface9* sSurf1, IDirect3DSurface9* sSurf2, IDirect3DTexture9* sTex2) { + for (int d = shadersSize - 1; d >= 0; d--) { + if (!shaders[d].Effect || !shaders[d].Active) continue; + if (shaders[d].mode2 && !(shaders[d].mode2 & GetLoopFlags())) continue; + if (shaders[d].mode & GetLoopFlags()) continue; + + if (shaders[d].ehTicks) shaders[d].Effect->SetInt(shaders[d].ehTicks, GetTickCount()); + UINT passes; + shaders[d].Effect->Begin(&passes, 0); + for (DWORD pass = 0; pass < passes; pass++) { + shaders[d].Effect->BeginPass(pass); + d3d9Device->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); + shaders[d].Effect->EndPass(); + d3d9Device->StretchRect(sSurf1, 0, sSurf2, 0, D3DTEXF_NONE); + d3d9Device->SetTexture(0, sTex2); + } + shaders[d].Effect->End(); + d3d9Device->SetTexture(0, sTex2); + } +} + +void ScriptShaders::OnResetDevice() { + for (DWORD d = 0; d < shadersSize; d++) { + if (shaders[d].Effect) shaders[d].Effect->OnResetDevice(); + } +} + +void ScriptShaders::OnLostDevice() { + for (DWORD d = 0; d < shadersSize; d++) { + if (shaders[d].Effect) shaders[d].Effect->OnLostDevice(); + } +} + +void ScriptShaders::Release() { + ResetShaders(); + for (DWORD d = 0; d < shaderTextures.size(); d++) shaderTextures[d]->Release(); + shaderTextures.clear(); +} + +void ScriptShaders::init() { + if (Graphics::mode) { + LoadGameHook::OnGameReset() += ResetShaders; + } +} + +} diff --git a/sfall/Modules/ScriptShaders.h b/sfall/Modules/ScriptShaders.h new file mode 100644 index 000000000..a76d0fea6 --- /dev/null +++ b/sfall/Modules/ScriptShaders.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Module.h" +#include "Graphics.h" + +namespace sfall +{ + +class ScriptShaders : public Module { +public: + const char* name() { return "ScriptShaders"; } + void init(); + //void exit() override; + + static size_t Count(); + + static void Refresh(IDirect3DSurface9* sSurf1, IDirect3DSurface9* sSurf2, IDirect3DTexture9* sTex2); + static void OnResetDevice(); + static void OnLostDevice(); + static void Release(); +}; + +int _stdcall LoadShader(const char*); +void _stdcall ActivateShader(DWORD); +void _stdcall DeactivateShader(DWORD); +void _stdcall FreeShader(DWORD); +void _stdcall SetShaderMode(DWORD d, DWORD mode); + +void _stdcall SetShaderInt(DWORD d, const char* param, int value); +void _stdcall SetShaderFloat(DWORD d, const char* param, float value); +void _stdcall SetShaderVector(DWORD d, const char* param, float f1, float f2, float f3, float f4); + +int _stdcall GetShaderTexture(DWORD d, DWORD id); +void _stdcall SetShaderTexture(DWORD d, const char* param, DWORD value); + +} diff --git a/sfall/Modules/Scripting/Arrays.cpp b/sfall/Modules/Scripting/Arrays.cpp index def6b441d..045667afd 100644 --- a/sfall/Modules/Scripting/Arrays.cpp +++ b/sfall/Modules/Scripting/Arrays.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include "..\ScriptExtender.h" @@ -22,7 +22,7 @@ ArraysMap arrays; // auto-incremented ID DWORD nextArrayID = 1; // temp arrays: set of arrayId -std::set tempArrays; +std::unordered_set tempArrays; // saved arrays: arrayKey => arrayId ArrayKeysMap savedArrays; // special array ID for array expressions @@ -407,6 +407,15 @@ void _stdcall FreeArray(DWORD id) { } } +void DeleteAllTempArrays() { + if (!tempArrays.empty()) { + for (std::unordered_set::iterator it = tempArrays.begin(); it != tempArrays.end(); ++it) { + FreeArray(*it); + } + tempArrays.clear(); + } +} + ScriptValue _stdcall GetArrayKey(DWORD id, int index) { if (arrays.find(id) == arrays.end() || index < -1 || index > arrays[id].size()) { return ScriptValue(0); diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index bdd2918a9..f2a457c90 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -2,7 +2,6 @@ #include #include -#include #include #include "ScriptValue.h" @@ -149,9 +148,6 @@ extern DWORD nextArrayID; extern DWORD arraysBehavior; -// temp arrays: set of arrayId -extern std::set tempArrays; - // saved arrays: arrayKey => arrayId extern ArrayKeysMap savedArrays; @@ -172,6 +168,9 @@ DWORD _stdcall TempArray(DWORD len, DWORD flags); // destroys array void _stdcall FreeArray(DWORD id); +// destroy all temp arrays +void DeleteAllTempArrays(); + /* op_get_array_key can be used to iterate over all keys in associative array */ diff --git a/sfall/Modules/Scripting/Handlers/Anims.cpp b/sfall/Modules/Scripting/Handlers/Anims.cpp index 8e037f67f..2e5ee370f 100644 --- a/sfall/Modules/Scripting/Handlers/Anims.cpp +++ b/sfall/Modules/Scripting/Handlers/Anims.cpp @@ -19,7 +19,6 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\..\SafeWrite.h" #include "..\..\Explosions.h" -#include "..\..\ScriptExtender.h" #include "..\OpcodeContext.h" #include "Anims.h" @@ -29,6 +28,23 @@ namespace sfall namespace script { +static char regAnimCombatCheck = 1; + +void RegAnimCombatCheck(DWORD newValue) { + char oldValue = regAnimCombatCheck; + regAnimCombatCheck = (newValue > 0); + if (oldValue != regAnimCombatCheck) { + SafeWrite8(0x459C97, regAnimCombatCheck); // reg_anim_func + SafeWrite8(0x459D4B, regAnimCombatCheck); // reg_anim_animate + SafeWrite8(0x459E3B, regAnimCombatCheck); // reg_anim_animate_reverse + SafeWrite8(0x459EEB, regAnimCombatCheck); // reg_anim_obj_move_to_obj + SafeWrite8(0x459F9F, regAnimCombatCheck); // reg_anim_obj_run_to_obj + SafeWrite8(0x45A053, regAnimCombatCheck); // reg_anim_obj_move_to_tile + SafeWrite8(0x45A10B, regAnimCombatCheck); // reg_anim_obj_run_to_tile + SafeWrite8(0x45AE53, regAnimCombatCheck); // reg_anim_animate_forever + } +} + // true if combat mode is active and combat check was not disabled bool checkCombatMode() { return (regAnimCombatCheck & fo::var::combat_state) != 0; @@ -105,7 +121,7 @@ void sf_explosions_metarule(OpcodeContext& ctx) { result = ExplosionsMetaruleFunc(mode, ctx.arg(1).asInt(), ctx.arg(2).asInt()); if (result == -1) { - ctx.printOpcodeError("ExplosionsMetarule() - mode (%d) is not supported for the function.", mode); + ctx.printOpcodeError("%s() - mode (%d) is not supported for the function.", ctx.getOpcodeName(), mode); } ctx.setReturn(result); } diff --git a/sfall/Modules/Scripting/Handlers/Anims.h b/sfall/Modules/Scripting/Handlers/Anims.h index 372aa5821..082bd1fc6 100644 --- a/sfall/Modules/Scripting/Handlers/Anims.h +++ b/sfall/Modules/Scripting/Handlers/Anims.h @@ -25,6 +25,8 @@ namespace script // new reg_anim functions (all using existing engine code) +void RegAnimCombatCheck(DWORD newValue); + class OpcodeContext; void sf_reg_anim_combat_check(OpcodeContext&); @@ -34,6 +36,7 @@ void sf_reg_anim_light(OpcodeContext&); void sf_reg_anim_change_fid(OpcodeContext&); void sf_reg_anim_take_out(OpcodeContext&); void sf_reg_anim_turn_towards(OpcodeContext&); + void sf_explosions_metarule(OpcodeContext&); void sf_art_cache_flush(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Arrays.cpp b/sfall/Modules/Scripting/Handlers/Arrays.cpp index ddc2501dc..32133d58b 100644 --- a/sfall/Modules/Scripting/Handlers/Arrays.cpp +++ b/sfall/Modules/Scripting/Handlers/Arrays.cpp @@ -31,9 +31,7 @@ namespace script void sf_create_array(OpcodeContext& ctx) { auto arrayId = CreateArray(ctx.arg(0).asInt(), ctx.arg(1).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, arrayId) - ); + ctx.setReturn(arrayId, DataType::INT); } void sf_set_array(OpcodeContext& ctx) { @@ -59,10 +57,10 @@ void sf_get_array(OpcodeContext& ctx) { auto str = Substring(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), 1); ctx.setReturn(str); } else { - ctx.printOpcodeError("get_array() - index must be numeric when used on a string."); + ctx.printOpcodeError("%s() - index must be numeric when used on a string.", ctx.getOpcodeName()); } } else { - ctx.printOpcodeError("get_array() - argument 0 must be an array ID or a string."); + ctx.printOpcodeError("%s() - argument 0 must be an array ID or a string.", ctx.getOpcodeName()); } } @@ -78,15 +76,13 @@ void sf_len_array(OpcodeContext& ctx) { void sf_resize_array(OpcodeContext& ctx) { if (ResizeArray(ctx.arg(0).asInt(), ctx.arg(1).asInt())) { - ctx.printOpcodeError("resize_array() - array sorting error."); + ctx.printOpcodeError("%s() - array sorting error.", ctx.getOpcodeName()); } } void sf_temp_array(OpcodeContext& ctx) { auto arrayId = TempArray(ctx.arg(0).asInt(), ctx.arg(1).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, arrayId) - ); + ctx.setReturn(arrayId, DataType::INT); } void sf_fix_array(OpcodeContext& ctx) { @@ -122,23 +118,34 @@ void sf_stack_array(OpcodeContext& ctx) { } // object LISTS - struct sList { fo::GameObject** obj; DWORD len; DWORD pos; - sList(const std::vector* vec) { + sList(const std::vector* vec) : pos(0) { len = vec->size(); obj = new fo::GameObject*[len]; for (size_t i = 0; i < len; i++) { obj[i] = (*vec)[i]; } - pos = 0; } }; +static DWORD listID = 0xCCCCCC; + +struct ListId { + sList* list; + DWORD id; + + ListId(sList* lst) : list(lst) { + id = ++listID; + } +}; +static std::vector mList; + static void FillListVector(DWORD type, std::vector& vec) { + vec.reserve(100); if (type == 6) { fo::ScriptInstance* scriptPtr; fo::GameObject* self_obj; @@ -155,22 +162,20 @@ static void FillListVector(DWORD type, std::vector& vec) { scriptPtr = fo::func::scr_find_next_at(); } } - } else if (type == 4) { + /*} else if (type == 4) { // TODO: verify code correctness - - /*for(int elv=0;elv<2;elv++) { + for(int elv=0;elv<2;elv++) { DWORD* esquares = &fo::var::squares[elv]; for(int tile=0;tile<10000;tile++) { esquares[tile]=0x8f000002; } }*/ - - } else { + } else if (type != 4) { for (int elv = 0; elv < 3; elv++) { for (int tile = 0; tile < 40000; tile++) { fo::GameObject* obj = fo::func::obj_find_first_at_tile(elv, tile); while (obj) { - DWORD otype = (obj->protoId & 0xff000000) >> 24; + DWORD otype = obj->Type(); if (type == 9 || (type == 0 && otype == 1) || (type == 1 && otype == 0) || (type >= 2 && type <= 5 && type == otype)) { vec.push_back(obj); } @@ -181,59 +186,64 @@ static void FillListVector(DWORD type, std::vector& vec) { } } -static void* _stdcall ListBegin(DWORD type) { +static DWORD ListBegin(DWORD type) { std::vector vec = std::vector(); FillListVector(type, vec); sList* list = new sList(&vec); - return list; + mList.emplace_back(list); + return listID; } -static DWORD _stdcall ListAsArray(DWORD type) { +static DWORD ListAsArray(DWORD type) { std::vector vec = std::vector(); FillListVector(type, vec); - DWORD id = TempArray(vec.size(), 4); - for (DWORD i = 0; i < vec.size(); i++) { + size_t sz = vec.size(); + DWORD id = TempArray(sz, 0); + for (size_t i = 0; i < sz; i++) { arrays[id].val[i].set((long)vec[i]); } return id; } -static fo::GameObject* _stdcall ListNext(sList* list) { - if (list->pos == list->len) return 0; +static fo::GameObject* ListNext(sList* list) { + if (!list || list->pos == list->len) return 0; else return list->obj[list->pos++]; } -static void _stdcall ListEnd(sList* list) { - delete[] list->obj; - delete list; +static void ListEnd(DWORD id) { + for (std::vector::const_iterator it = mList.cbegin(), it_end = mList.cend(); it != it_end; ++it) { + if (it->id == id) { + delete[] it->list->obj; + delete it->list; + mList.erase(it); + break; + } + } } void sf_list_begin(OpcodeContext& ctx) { - auto list = ListBegin(ctx.arg(0).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, reinterpret_cast(list)) - ); + ctx.setReturn(ListBegin(ctx.arg(0).rawValue()), DataType::INT); } void sf_list_as_array(OpcodeContext& ctx) { - auto arrayId = ListAsArray(ctx.arg(0).asInt()); - ctx.setReturn( - ScriptValue(DataType::INT, arrayId) - ); + auto arrayId = ListAsArray(ctx.arg(0).rawValue()); + ctx.setReturn(arrayId, DataType::INT); } void sf_list_next(OpcodeContext& ctx) { - // TODO: make it safer - auto list = reinterpret_cast(ctx.arg(0).rawValue()); - ctx.setReturn( - ListNext(list) - ); + auto id = ctx.arg(0).rawValue(); + sList* list = nullptr; + for (std::vector::const_iterator it = mList.cbegin(), it_end = mList.cend(); it != it_end; ++it) { + if (it->id == id) { + list = it->list; + break; + } + } + ctx.setReturn(ListNext(list)); } void sf_list_end(OpcodeContext& ctx) { - // TODO: make it safer - auto list = reinterpret_cast(ctx.arg(0).rawValue()); - ListEnd(list); + ListEnd(ctx.arg(0).rawValue()); } } diff --git a/sfall/Modules/Scripting/Handlers/Core.cpp b/sfall/Modules/Scripting/Handlers/Core.cpp index a43f1f0bf..08dc20c97 100644 --- a/sfall/Modules/Scripting/Handlers/Core.cpp +++ b/sfall/Modules/Scripting/Handlers/Core.cpp @@ -77,7 +77,7 @@ void __declspec(naked) op_available_global_script_types() { void sf_set_sfall_global(OpcodeContext& ctx) { if (ctx.arg(0).isString()) { if (SetGlobalVar(ctx.arg(0).strValue(), ctx.arg(1).rawValue())) { - ctx.printOpcodeError("set_sfall_global() - The name of the global variable must consist of 8 characters."); + ctx.printOpcodeError("%s() - the name of the global variable must consist of 8 characters.", ctx.getOpcodeName()); } } else { SetGlobalVarInt(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); @@ -87,7 +87,7 @@ void sf_set_sfall_global(OpcodeContext& ctx) { static long GetGlobalVarNameString(OpcodeContext& ctx) { const char* var = ctx.arg(0).strValue(); if (strlen(var) != 8) { - ctx.printOpcodeError("get_sfall_global() - The name of the global variable must consist of 8 characters."); + ctx.printOpcodeError("%s() - the name of the global variable must consist of 8 characters.", ctx.getOpcodeName()); return 0; } return GetGlobalVarInternal(*(__int64*)var); diff --git a/sfall/Modules/Scripting/Handlers/Graphics.cpp b/sfall/Modules/Scripting/Handlers/Graphics.cpp index 53a8c64bb..39d7d2e50 100644 --- a/sfall/Modules/Scripting/Handlers/Graphics.cpp +++ b/sfall/Modules/Scripting/Handlers/Graphics.cpp @@ -21,6 +21,7 @@ #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\Graphics.h" #include "..\..\ScriptExtender.h" +#include "..\..\ScriptShaders.h" #include "Graphics.h" diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 0237a15b0..d50ea9b2b 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -151,46 +151,11 @@ void __declspec(naked) op_resume_game() { } } -void __declspec(naked) op_create_message_window() { - __asm { - pushad - mov ebx, dword ptr ds:[FO_VAR_curr_font_num]; - cmp ebx, 0x65; - je end; - - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; -next: - mov ebx, eax; - mov eax, ecx; - call fo::funcoffs::interpretGetString_; - mov esi, eax; - - mov ecx, eax; - mov eax, 3; - push 1; // arg10 - mov al, byte ptr ds:[0x6AB718]; - push eax; // a9 - push 0; // *DisplayText - push eax; // ColorIndex - push 0x74; // y - mov ecx, 0xC0; // x - mov eax, esi; // text - xor ebx, ebx; // ? - xor edx, edx; // ? - call fo::funcoffs::dialog_out_; - //xor eax, eax; -end: - popad; - ret; - } +void sf_create_message_window(OpcodeContext &ctx) { + const char* str = ctx.arg(0).strValue(); + if (!str || str[0] == 0) return; + //if (fo::var::curr_font_num == 101) return; + fo::func::DialogOut(str); } void __declspec(naked) op_get_viewport_x() { @@ -224,8 +189,8 @@ void __declspec(naked) op_set_viewport_x() { _GET_ARG_INT(end); mov ds:[FO_VAR_wmWorldOffsetX], eax; end: - pop edx; - pop ecx; + pop edx; + pop ecx; retn; } } @@ -245,7 +210,7 @@ void __declspec(naked) op_set_viewport_y() { 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."); + if (result == -1) ctx.printOpcodeError("%s() - cannot add new tag as the maximum limit of 126 tags has been reached.", ctx.getMetaruleName()); ctx.setReturn(result); } @@ -347,12 +312,12 @@ void sf_set_iface_tag_text(OpcodeContext& ctx) { if (boxTag > 4 && boxTag <= maxBox) { 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); + ctx.printOpcodeError("%s() - tag value must be in the range of 5 to %d.", ctx.getMetaruleName(), maxBox); } } void sf_inventory_redraw(OpcodeContext& ctx) { - int mode = -1; + int mode; DWORD loopFlag = GetLoopFlags(); if (loopFlag & INVENTORY) { mode = 0; @@ -367,13 +332,11 @@ void sf_inventory_redraw(OpcodeContext& ctx) { } if (!ctx.arg(0).asBool()) { - int* stack_offset = (int*)FO_VAR_stack_offset; - stack_offset[fo::var::curr_stack * 4] = 0; + fo::var::stack_offset[fo::var::curr_stack] = 0; fo::func::display_inventory(0, -1, mode); } else if (mode >= 2) { - int* target_stack_offset = (int*)FO_VAR_target_stack_offset; - target_stack_offset[fo::var::target_curr_stack * 4] = 0; - fo::func::display_target_inventory(0, -1, (DWORD*)fo::var::target_pud, mode); + fo::var::target_stack_offset[fo::var::target_curr_stack] = 0; + fo::func::display_target_inventory(0, -1, fo::var::target_pud, mode); fo::func::win_draw(fo::var::i_wid); } } @@ -396,8 +359,93 @@ void sf_create_win(OpcodeContext& ctx) { ctx.arg(3).asInt(), ctx.arg(4).asInt(), // w, h 256, flags) == -1) { - ctx.printOpcodeError("create_win() - couldn't create window."); + ctx.printOpcodeError("%s() - couldn't create window.", ctx.getMetaruleName()); + } +} + +static void DrawImage(OpcodeContext& ctx, bool isScaled) { + if (*(DWORD*)FO_VAR_currentWindow == -1) { + ctx.printOpcodeError("%s() - no created/selected window for the image.", ctx.getMetaruleName()); + return; + } + long direction = 0; + const char* file = nullptr; + if (ctx.arg(0).isInt()) { // art id + long fid = ctx.arg(0).rawValue(); + if (fid == -1) return; + long _fid = fid & 0xFFFFFFF; + file = fo::func::art_get_name(_fid); // .frm + if (_fid >> 24 == fo::OBJ_TYPE_CRITTER) { + direction = (fid >> 28); + DWORD sz; + if (direction && fo::func::db_file_exist(file, &sz)) { + file = fo::func::art_get_name(fid); // .fr# + } + } + } else { + file = ctx.arg(0).strValue(); // path to frm file + } + fo::FrmFile* frmPtr = nullptr; + if (fo::func::load_frame(file, &frmPtr)) { + ctx.printOpcodeError("%s() - cannot open the file: %s", ctx.getMetaruleName(), file); + return; } + fo::FrmFrameData* framePtr = (fo::FrmFrameData*)&frmPtr->width; + if (direction > 0 && direction < 6) { + BYTE* offsOriFrame = (BYTE*)framePtr; + offsOriFrame += frmPtr->oriFrameOffset[direction]; + framePtr = (fo::FrmFrameData*)offsOriFrame; + } + int frameno = ctx.arg(1).rawValue(); + if (frameno > 0) { + int maxFrames = frmPtr->frames - 1; + if (frameno > maxFrames) frameno = maxFrames; + while (frameno-- > 0) { + BYTE* offsFrame = (BYTE*)framePtr; + offsFrame += framePtr->size + (sizeof(fo::FrmFrameData) - 1); + framePtr = (fo::FrmFrameData*)offsFrame; + } + } + if (isScaled && ctx.numArgs() < 3) { + fo::func::displayInWindow(framePtr->width, framePtr->width, framePtr->height, framePtr->data); // scaled to window size (w/o transparent) + } else { + int x = ctx.arg(2).rawValue(), y = ctx.arg(3).rawValue(); + if (isScaled) { // draw to scale + long s_width, s_height; + if (ctx.numArgs() < 5) { + s_width = framePtr->width; + s_height = framePtr->height; + } else { + s_width = ctx.arg(4).rawValue(); + s_height = (ctx.numArgs() > 5) ? ctx.arg(5).rawValue() : -1; + } + // scale with aspect ratio if w or h is set to -1 + if (s_width <= -1 && s_height > 0) { + s_width = s_height * framePtr->width / framePtr->height; + } else if (s_height <= -1 && s_width > 0) { + s_height = s_width * framePtr->height / framePtr->width; + } + if (s_width <= 0 || s_height <= 0) return; + + long w_width = fo::func::windowWidth(); + long xy_pos = (y * w_width) + x; + fo::func::trans_cscale(framePtr->width, framePtr->height, s_width, s_height, xy_pos, w_width, framePtr->data); // custom scaling + } else { // with x/y frame offsets + fo::func::windowDisplayBuf(x + frmPtr->xshift[direction], framePtr->width, y + frmPtr->yshift[direction], framePtr->height, framePtr->data, ctx.arg(4).rawValue()); + } + } + __asm { + mov eax, frmPtr; + call fo::funcoffs::mem_free_; + } +} + +void sf_draw_image(OpcodeContext& ctx) { + DrawImage(ctx, false); +} + +void sf_draw_image_scaled(OpcodeContext& ctx) { + DrawImage(ctx, true); } } diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index 260463c6f..f6dcaa026 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -57,7 +57,7 @@ void __declspec() op_stop_game(); void __declspec() op_resume_game(); //Create a message window with given string -void __declspec() op_create_message_window(); +void sf_create_message_window(OpcodeContext&); void __declspec() op_get_viewport_x(); @@ -99,5 +99,9 @@ void sf_dialog_message(OpcodeContext&); void sf_create_win(OpcodeContext&); +void sf_draw_image(OpcodeContext&); + +void sf_draw_image_scaled(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Memory.cpp b/sfall/Modules/Scripting/Handlers/Memory.cpp index 685b3bc6a..54ac94581 100644 --- a/sfall/Modules/Scripting/Handlers/Memory.cpp +++ b/sfall/Modules/Scripting/Handlers/Memory.cpp @@ -39,7 +39,7 @@ void __declspec(naked) op_read_byte() { call fo::funcoffs::interpretPopLong_; cmp dx, VAR_TYPE_INT; jnz error; - movzx edx, byte ptr ds : [eax]; + movzx edx, byte ptr ds:[eax]; jmp result; error: mov edx, 0; @@ -68,7 +68,7 @@ void __declspec(naked) op_read_short() { call fo::funcoffs::interpretPopLong_; cmp dx, VAR_TYPE_INT; jnz error; - movzx edx, word ptr ds : [eax]; + movzx edx, word ptr ds:[eax]; jmp result; error: mov edx, 0; @@ -97,7 +97,7 @@ void __declspec(naked) op_read_int() { call fo::funcoffs::interpretPopLong_; cmp dx, VAR_TYPE_INT; jnz error; - mov edx, dword ptr ds : [eax]; + mov edx, dword ptr ds:[eax]; jmp result; error: mov edx, 0; @@ -225,7 +225,7 @@ void __declspec(naked) op_write_int() { call SafeWrite32; end: popad - retn; + retn; } } @@ -233,7 +233,7 @@ static void _stdcall WriteStringInternal(const char* str, char* addr) { bool hitnull = false; while (*str) { if (!*addr) hitnull = true; - if (hitnull&&addr[1]) break; + if (hitnull && addr[1]) break; *addr = *str; addr++; str++; @@ -295,7 +295,7 @@ static void _stdcall CallOffsetInternal(DWORD func, DWORD script) { call fo::funcoffs::interpretPopLong_; lea ecx, args; add ecx, i; - mov[ecx], eax; + mov [ecx], eax; } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index a30fa78c7..7ee89042b 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -24,6 +24,7 @@ #include "Interface.h" #include "Misc.h" #include "Objects.h" +#include "Perks.h" #include "Utils.h" #include "Worldmap.h" @@ -41,30 +42,10 @@ namespace script // Use ctx.setReturn(x) to set return value. // If you want to call user-defined procedures in your handler, use RunScriptProc(). -struct SfallMetarule { - // function name - const char* name; - - // pointer to handler function - ScriptingFunctionHandler func; - - // minimum number of arguments - short minArgs; - - // maximum number of arguments - short maxArgs; - - // argument validation settings - OpcodeArgumentType argValidation[OP_MAX_ARGUMENTS]; -}; - typedef std::unordered_map MetaruleTableType; static MetaruleTableType metaruleTable; -// currently executed metarule -static const SfallMetarule* currentMetarule; - /* Metarule AKA sfall_funcX. @@ -74,7 +55,7 @@ static const SfallMetarule* currentMetarule; { name, handler, minArgs, maxArgs, {arg1, arg2, ...} } - name - name of function that will be used in scripts, - handler - pointer to handler function (see examples below), - - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function + - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function (max 6) - arg1, arg2, ... - argument types for automatic validation */ static const SfallMetarule metarules[] = { @@ -86,7 +67,9 @@ static const SfallMetarule metarules[] = { {"critter_inven_obj2", sf_critter_inven_obj2, 2, 2, {ARG_OBJECT, ARG_INT}}, {"dialog_message", sf_dialog_message, 1, 1, {ARG_STRING}}, {"dialog_obj", sf_get_dialog_object, 0, 0}, - {"display_stats", sf_display_stats, 0, 0}, + {"display_stats", sf_display_stats, 0, 0}, // refresh + {"draw_image", sf_draw_image, 1, 5, {ARG_INTSTR, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, + {"draw_image_scaled", sf_draw_image_scaled, 1, 6, {ARG_INTSTR, ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {"exec_map_update_scripts", sf_exec_map_update_scripts, 0, 0}, {"floor2", sf_floor2, 1, 1, {ARG_NUMBER}}, {"get_can_rest_on_map", sf_get_rest_on_map, 2, 2, {ARG_INT, ARG_INT}}, @@ -101,6 +84,8 @@ static const SfallMetarule metarules[] = { {"get_object_data", sf_get_object_data, 2, 2, {ARG_OBJECT, ARG_INT}}, {"get_outline", sf_get_outline, 1, 1, {ARG_OBJECT}}, {"get_string_pointer", sf_get_string_pointer, 1, 1, {ARG_STRING}}, + {"has_fake_perk_npc", sf_has_fake_perk_npc, 2, 2, {ARG_OBJECT, ARG_STRING}}, + {"has_fake_trait_npc", sf_has_fake_trait_npc, 2, 2, {ARG_OBJECT, ARG_STRING}}, {"intface_hide", sf_intface_hide, 0, 0}, {"intface_is_hidden", sf_intface_is_hidden, 0, 0}, {"intface_redraw", sf_intface_redraw, 0, 0}, @@ -119,6 +104,8 @@ static const SfallMetarule metarules[] = { {"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_fake_perk_npc", sf_set_fake_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {"set_fake_trait_npc", sf_set_fake_trait_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"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}}, {"set_ini_setting", sf_set_ini_setting, 2, 2, {ARG_STRING, ARG_INTSTR}}, @@ -127,6 +114,7 @@ static const SfallMetarule metarules[] = { {"set_outline", sf_set_outline, 2, 2, {ARG_OBJECT, ARG_INT}}, {"set_rest_heal_time", sf_set_rest_heal_time, 1, 1, {ARG_INT}}, {"set_rest_mode", sf_set_rest_mode, 1, 1, {ARG_INT}}, + {"set_selectable_perk_npc", sf_set_selectable_perk_npc, 5, 5, {ARG_OBJECT, ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {"set_unique_id", sf_set_unique_id, 1, 2, {ARG_OBJECT, ARG_INT}}, {"set_unjam_locks_time", sf_set_unjam_locks_time, 1, 1, {ARG_INT}}, {"spatial_radius", sf_spatial_radius, 1, 1, {ARG_OBJECT}}, @@ -156,11 +144,13 @@ void InitMetaruleTable() { // Validates arguments against metarule specification. // On error prints to debug.log and returns false. -static bool ValidateMetaruleArguments(OpcodeContext& ctx, const SfallMetarule* metaruleInfo) { +static bool ValidateMetaruleArguments(OpcodeContext& ctx) { + const SfallMetarule* metaruleInfo = ctx.metarule; int argCount = ctx.numArgs(); if (argCount < metaruleInfo->minArgs || argCount > metaruleInfo->maxArgs) { ctx.printOpcodeError( - "sfall_funcX(\"%s\", ...) - invalid number of arguments (%d), must be from %d to %d.", + "%s(\"%s\", ...) - invalid number of arguments (%d), must be from %d to %d.", + ctx.getOpcodeName(), metaruleInfo->name, argCount, metaruleInfo->minArgs, @@ -178,20 +168,20 @@ void HandleMetarule(OpcodeContext& ctx) { const char* name = nameArg.strValue(); MetaruleTableType::iterator lookup = metaruleTable.find(name); if (lookup != metaruleTable.end()) { - currentMetarule = lookup->second; + ctx.metarule = lookup->second; // shift function name away, so argument #0 will correspond to actual first argument of function // this allows to use the same handlers for opcodes and metarule functions ctx.setArgShift(1); - if (ValidateMetaruleArguments(ctx, currentMetarule)) { - currentMetarule->func(ctx); + if (ValidateMetaruleArguments(ctx)) { + ctx.metarule->func(ctx); } else if (ctx.hasReturn()) { ctx.setReturn(-1); } } else { - ctx.printOpcodeError("sfall_funcX(name, ...) - name '%s' is unknown.", name); + ctx.printOpcodeError("%s(\"%s\", ...) - metarule function is unknown.", ctx.getOpcodeName(), name); } } else { - ctx.printOpcodeError("sfall_funcX(name, ...) - name must be string."); + ctx.printOpcodeError("%s(name, ...) - name must be string.", ctx.getOpcodeName()); } } diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index 8643ec66e..9663bedc6 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -296,16 +296,12 @@ void sf_set_object_knockback(OpcodeContext& ctx) { fo::GameObject* object = ctx.arg(0).asObject(); if (mode) { if (object->Type() != fo::OBJ_TYPE_CRITTER) { - if (mode == 1) { - ctx.printOpcodeError("set_target_knockback() - the object is not a critter."); - } else { - ctx.printOpcodeError("set_attacker_knockback() - the object is not a critter."); - } + ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getOpcodeName()); return; } } else { if (object->Type() != fo::OBJ_TYPE_ITEM) { - ctx.printOpcodeError("set_weapon_knockback() - the object is not an item."); + ctx.printOpcodeError("%s() - the object is not an item.", ctx.getOpcodeName()); return; } } @@ -547,106 +543,30 @@ static int ParseIniSetting(const char* iniString, const char* &key, char section return 1; } -static char IniStrBuffer[128]; -static DWORD _stdcall GetIniSetting2(const char* c, DWORD string) { +static char IniStrBuffer[256]; +static DWORD GetIniSetting(const char* str, bool isString) { const char* key; char section[33], file[67]; - if (ParseIniSetting(c, key, section, file) < 0) { + if (ParseIniSetting(str, key, section, file) < 0) { return -1; } - if (string) { + if (isString) { IniStrBuffer[0] = 0; - GetPrivateProfileStringA(section, key, "", IniStrBuffer, 128, file); + GetPrivateProfileStringA(section, key, "", IniStrBuffer, 256, file); return (DWORD)&IniStrBuffer[0]; } else { return GetPrivateProfileIntA(section, key, -1, file); } } -void __declspec(naked) op_get_ini_setting() { - __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 error; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push 0; - push eax; - call GetIniSetting2; - mov edx, eax; - jmp result; -error: - mov edx, -1; -result: - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov edx, VAR_TYPE_INT; - mov eax, edi; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } +void sf_get_ini_setting(OpcodeContext& ctx) { + ctx.setReturn(GetIniSetting(ctx.arg(0).strValue(), false)); } -void __declspec(naked) op_get_ini_string() { - __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 error; -next: - mov ebx, eax; - mov eax, edi; - call fo::funcoffs::interpretGetString_; - push 1; - push eax; - call GetIniSetting2; - mov edx, eax; - mov eax, edi; - call fo::funcoffs::interpretAddString_; - mov edx, eax; - mov ebx, VAR_TYPE_STR; - jmp result; -error: - xor edx, edx; - mov ebx, VAR_TYPE_INT -result: - mov eax, edi; - call fo::funcoffs::interpretPushLong_; - mov edx, ebx; - mov eax, edi; - call fo::funcoffs::interpretPushShort_; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; - } +void sf_get_ini_string(OpcodeContext& ctx) { + DWORD result = GetIniSetting(ctx.arg(0).strValue(), true); + ctx.setReturn(result, (result != -1) ? DataType::STR : DataType::INT); } void __declspec(naked) op_get_uptime() { @@ -658,8 +578,8 @@ void __declspec(naked) op_get_uptime() { mov edx, eax; pop eax; _RET_VAL_INT(ecx); - pop edx; - pop ecx; + pop edx; + pop ecx; retn; } } @@ -763,7 +683,7 @@ void __declspec(naked) op_set_bodypart_hit_modifier() { } } -static char* valueOutRange = "argument values out of range"; +static char* valueOutRange = "%s() - argument values out of range"; void sf_set_critical_table(OpcodeContext& ctx) { DWORD critter = ctx.arg(0).asInt(), @@ -772,7 +692,7 @@ void sf_set_critical_table(OpcodeContext& ctx) { element = ctx.arg(3).asInt(); if (critter >= Criticals::critTableCount || bodypart >= 9 || slot >= 6 || element >= 7) { - ctx.printOpcodeError("set_critical_table() - %s.", valueOutRange); + ctx.printOpcodeError(valueOutRange, ctx.getOpcodeName()); } else { Criticals::SetCriticalTable(critter, bodypart, slot, element, ctx.arg(4).asInt()); } @@ -785,7 +705,7 @@ void sf_get_critical_table(OpcodeContext& ctx) { element = ctx.arg(3).asInt(); if (critter >= Criticals::critTableCount || bodypart >= 9 || slot >= 6 || element >= 7) { - ctx.printOpcodeError("get_critical_table() - %s.", valueOutRange); + ctx.printOpcodeError(valueOutRange, ctx.getOpcodeName()); } else { ctx.setReturn(Criticals::GetCriticalTable(critter, bodypart, slot, element)); } @@ -798,7 +718,7 @@ void sf_reset_critical_table(OpcodeContext& ctx) { element = ctx.arg(3).asInt(); if (critter >= Criticals::critTableCount || bodypart >= 9 || slot >= 6 || element >= 7) { - ctx.printOpcodeError("reset_critical_table() - %s.", valueOutRange); + ctx.printOpcodeError(valueOutRange, ctx.getOpcodeName()); } else { Criticals::ResetCriticalTable(critter, bodypart, slot, element); } @@ -903,7 +823,7 @@ void __declspec(naked) op_nb_create_char() { } } -static char* failedLoad = "failed to load a prototype id"; +static const char* failedLoad = "%s() - failed to load a prototype id: %d"; static bool protoMaxLimitPatch = false; void sf_get_proto_data(OpcodeContext& ctx) { @@ -913,7 +833,7 @@ void sf_get_proto_data(OpcodeContext& ctx) { if (result != -1) { result = *(long*)((BYTE*)protoPtr + ctx.arg(1).rawValue()); } else { - ctx.printOpcodeError("get_proto_data() - %s: %d", failedLoad, pid); + ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); } ctx.setReturn(result); } @@ -926,7 +846,7 @@ void sf_set_proto_data(OpcodeContext& ctx) { protoMaxLimitPatch = true; } } else { - ctx.printOpcodeError("set_proto_data() - %s: %d", failedLoad, pid); + ctx.printOpcodeError(failedLoad, ctx.getOpcodeName(), pid); } } @@ -1330,47 +1250,51 @@ void sf_exec_map_update_scripts(OpcodeContext& ctx) { } void sf_set_ini_setting(OpcodeContext& ctx) { - const char* iniString = ctx.arg(0).asString(); const ScriptValue &argVal = ctx.arg(1); + const char* saveValue; if (argVal.isInt()) { _itoa_s(argVal.rawValue(), IniStrBuffer, 10); + saveValue = IniStrBuffer; } else { - strcpy_s(IniStrBuffer, argVal.asString()); + saveValue = argVal.strValue(); } - const char* key; char section[33], file[67]; - int result = ParseIniSetting(iniString, key, section, file); + int result = ParseIniSetting(ctx.arg(0).strValue(), key, section, file); if (result > 0) { - result = WritePrivateProfileString(section, key, IniStrBuffer, file); + result = WritePrivateProfileString(section, key, saveValue, file); } switch (result) { case 0: - ctx.printOpcodeError("set_ini_setting() - value save error."); + ctx.printOpcodeError("%s() - value save error.", ctx.getMetaruleName()); break; case -1: - ctx.printOpcodeError("set_ini_setting() - invalid setting argument."); + ctx.printOpcodeError("%s() - invalid setting argument.", ctx.getMetaruleName()); break; } } -char getIniSectionBuf[512]; +char getIniSectionBuf[5120]; + void sf_get_ini_sections(OpcodeContext& ctx) { auto fileName = std::string(".\\") + ctx.arg(0).asString(); - GetPrivateProfileSectionNamesA(getIniSectionBuf, 512, fileName.data()); + GetPrivateProfileSectionNamesA(getIniSectionBuf, 5120, fileName.data()); std::vector sections; char* section = getIniSectionBuf; while (*section != 0) { sections.push_back(section); section += std::strlen(section) + 1; } - int arrayId = TempArray(sections.size(), 0); + size_t sz = sections.size(); + int arrayId = TempArray(sz, 0); auto& arr = arrays[arrayId]; - for (size_t i = 0; i < sections.size(); ++i) { - arr.val[i].set(sections[i]); + for (size_t i = 0; i < sz; ++i) { + size_t j = i + 1; + int len = (j < sz) ? sections[j] - sections[i] - 1 : -1; + arr.val[i].set(sections[i], len); } ctx.setReturn(arrayId); } @@ -1378,7 +1302,7 @@ void sf_get_ini_sections(OpcodeContext& ctx) { void sf_get_ini_section(OpcodeContext& ctx) { auto fileName = std::string(".\\") + ctx.arg(0).asString(); auto section = ctx.arg(1).asString(); - GetPrivateProfileSectionA(section, getIniSectionBuf, 512, fileName.data()); + GetPrivateProfileSectionA(section, getIniSectionBuf, 5120, fileName.data()); int arrayId = TempArray(-1, 0); // associative auto& arr = arrays[arrayId]; char *key = getIniSectionBuf, *val = nullptr; diff --git a/sfall/Modules/Scripting/Handlers/Misc.h b/sfall/Modules/Scripting/Handlers/Misc.h index 799ca360a..5a29befff 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.h +++ b/sfall/Modules/Scripting/Handlers/Misc.h @@ -66,9 +66,9 @@ void sf_inc_npc_level(OpcodeContext&); void sf_get_npc_level(OpcodeContext&); -void __declspec() op_get_ini_setting(); +void sf_get_ini_setting(OpcodeContext&); -void __declspec() op_get_ini_string(); +void sf_get_ini_string(OpcodeContext&); void __declspec() op_get_uptime(); diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 079016a22..2994180a0 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -328,7 +328,7 @@ void sf_critter_inven_obj2(OpcodeContext& ctx) { ctx.setReturn(critter->invenSize); break; default: - ctx.printOpcodeError("critter_inven_obj2() - invalid type."); + ctx.printOpcodeError("%s() - invalid type.", ctx.getMetaruleName()); } } @@ -365,10 +365,13 @@ void sf_item_weight(OpcodeContext& ctx) { void sf_set_dude_obj(OpcodeContext& ctx) { auto obj = ctx.arg(0).asObject(); if (obj->Type() == fo::OBJ_TYPE_CRITTER) { - PartyControl::SwitchToCritter(obj); + //if (!InCombat && obj != PartyControl::RealDudeObject()) { + // ctx.printOpcodeError("%s() - controlling of the critter is only allowed in combat mode.", ctx.getMetaruleName()); + //} else { + PartyControl::SwitchToCritter(obj); + //} } else { - ctx.printOpcodeError("Object is not a critter!"); - ctx.setReturn(-1); + ctx.printOpcodeError("%s() - the object is not a critter.", ctx.getMetaruleName()); } } @@ -391,7 +394,7 @@ void sf_unjam_lock(OpcodeContext& ctx) { void sf_set_unjam_locks_time(OpcodeContext& ctx) { int time = ctx.arg(0).asInt(); if (time < 0 || time > 127) { - ctx.printOpcodeError("set_unjam_locks_time() - time argument must be in the range of 0 to 127."); + ctx.printOpcodeError("%s() - time argument must be in the range of 0 to 127.", ctx.getMetaruleName()); } else { Objects::SetAutoUnjamLockTime(time); } @@ -405,13 +408,13 @@ void sf_item_make_explosive(OpcodeContext& ctx) { if (min > max) { max = min; - ctx.printOpcodeError("item_make_explosive() - Warning: value of max argument is less than the min argument."); + ctx.printOpcodeError("%s() - Warning: value of max argument is less than the min argument.", ctx.getMetaruleName()); } if (pid > 0 && pidActive > 0) { Explosions::AddToExplosives(pid, pidActive, min, max); } else { - ctx.printOpcodeError("item_make_explosive() - PID arguments must be greater than 0."); + ctx.printOpcodeError("%s() - invalid PID number, must be greater than 0.", ctx.getMetaruleName()); } } @@ -495,7 +498,7 @@ void sf_get_object_ai_data(OpcodeContext& ctx) { value = arrayId; break; default: - ctx.printOpcodeError("get_object_ai_data() - invalid value for AI argument."); + ctx.printOpcodeError("%s() - invalid value for AI argument.", ctx.getMetaruleName()); } ctx.setReturn(value, DataType::INT); } @@ -513,10 +516,10 @@ void sf_set_drugs_data(OpcodeContext& ctx) { result = Drugs::SetDrugAddictTimeOff(pid, val); break; default: - ctx.printOpcodeError("set_drugs_data() - invalid value for type argument."); + ctx.printOpcodeError("%s() - invalid value for type argument.", ctx.getMetaruleName()); return; } - if (result) ctx.printOpcodeError("set_drugs_data() - drug PID not found in the configuration file."); + if (result) ctx.printOpcodeError("%s() - drug PID not found in the configuration file.", ctx.getMetaruleName()); } void sf_set_unique_id(OpcodeContext& ctx) { diff --git a/sfall/Modules/Scripting/Handlers/Perks.cpp b/sfall/Modules/Scripting/Handlers/Perks.cpp index 425cef5c5..a470bfa8d 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.cpp +++ b/sfall/Modules/Scripting/Handlers/Perks.cpp @@ -27,7 +27,6 @@ namespace sfall { namespace script { -using namespace fo; void __declspec(naked) op_get_perk_owed() { __asm { @@ -183,218 +182,44 @@ void __declspec(naked) op_set_perk_value() { } } -void __declspec(naked) op_set_selectable_perk() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds : [esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds : [esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +void sf_set_selectable_perk(OpcodeContext& ctx) { + Perks::SetSelectablePerk(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} - call SetSelectablePerk; -fail: - add esp, 32; - popad; - retn; - } +void sf_set_fake_perk(OpcodeContext& ctx) { + Perks::SetFakePerk(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); } -void __declspec(naked) op_set_fake_perk() { - __asm { - pushad; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; +void sf_set_fake_trait(OpcodeContext& ctx) { + Perks::SetFakeTrait(ctx.arg(0).strValue(), ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).strValue()); +} - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds : [esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds : [esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +const char* notPartyMemberErr = "%s() - the object is not a party member."; - call SetFakePerk; -fail: - add esp, 32; - popad; - retn; +void sf_set_selectable_perk_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetSelectablePerk(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } -void __declspec(naked) op_set_fake_trait() { - __asm { - push ebx; - push ecx; - push edx; - push edi; - push esi; - mov ecx, eax; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopShort_; - push eax; - mov eax, ecx; - call fo::funcoffs::interpretPopLong_; - push eax; - - movzx eax, word ptr[esp + 12]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr[esp + 20]; - cmp eax, VAR_TYPE_INT; - jne fail; - movzx eax, word ptr ds : [esp + 4]; - cmp eax, VAR_TYPE_STR2; - je next1; - cmp eax, VAR_TYPE_STR; - jne fail; -next1: - movzx eax, word ptr ds : [esp + 28]; - cmp eax, VAR_TYPE_STR2; - je next2; - cmp eax, VAR_TYPE_STR; - jne fail; -next2: - mov eax, ecx; - mov edx, [esp + 28]; - mov ebx, [esp + 24]; - call fo::funcoffs::interpretGetString_; - push eax; - mov eax, [esp + 20]; - push eax; - mov eax, [esp + 16]; - push eax; - mov eax, ecx; - mov edx, [esp + 16]; - mov ebx, [esp + 12]; - call fo::funcoffs::interpretGetString_; - push eax; +void sf_set_fake_perk_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetFakePerk(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); + } +} - call SetFakeTrait; -fail: - add esp, 32; - pop esi; - pop edi; - pop edx; - pop ecx; - pop ebx; - retn; +void sf_set_fake_trait_npc(OpcodeContext& ctx) { + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + Perks::SetFakeTrait(ctx.arg(1).strValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } } @@ -402,28 +227,24 @@ void __declspec(naked) op_set_perkbox_title() { __asm { push ebx; push ecx; - push edx; - push edi; - mov edi, eax; + mov ecx, eax; call fo::funcoffs::interpretPopShort_; - mov edx, eax; - mov eax, edi; + mov edx, eax; + mov eax, ecx; call fo::funcoffs::interpretPopLong_; - cmp dx, VAR_TYPE_STR2; - jz next; - cmp dx, VAR_TYPE_STR; - jnz end; + cmp dx, VAR_TYPE_STR2; + jz next; + cmp dx, VAR_TYPE_STR; + jnz end; next: - mov ebx, eax; - mov eax, edi; + mov ebx, eax; + mov eax, ecx; call fo::funcoffs::interpretGetString_; push eax; call SetPerkboxTitle; end: - pop edi; - pop edx; - pop ecx; - pop ebx; + pop ecx; + pop ebx; retn; } } @@ -456,43 +277,33 @@ void __declspec(naked) op_clear_selectable_perks() { } void sf_has_fake_perk(OpcodeContext& ctx) { - ctx.setReturn(HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); + ctx.setReturn(Perks::HasFakePerk(ctx.arg(0).asString(), ctx.arg(0).asInt())); } -void __declspec(naked) op_has_fake_trait() { - __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 HasFakeTrait; -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_trait(OpcodeContext& ctx) { + ctx.setReturn(Perks::HasFakeTrait(ctx.arg(0).strValue())); +} + +void sf_has_fake_perk_npc(OpcodeContext& ctx) { + long result = 0; + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + result = Perks::HasFakePerkOwner(ctx.arg(1).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); } + ctx.setReturn(result); +} + +void sf_has_fake_trait_npc(OpcodeContext& ctx) { + long result = 0; + auto obj = ctx.arg(0).asObject(); + if (obj->Type() == fo::ObjType::OBJ_TYPE_CRITTER && fo::func::isPartyMember(obj)) { + result = Perks::HasFakeTraitOwner(ctx.arg(1).strValue(), (obj->id != PLAYER_ID) ? obj->id : 0); + } else { + ctx.printOpcodeError(notPartyMemberErr, ctx.getMetaruleName()); + } + ctx.setReturn(result); } void __declspec(naked) op_perk_add_mode() { @@ -511,28 +322,24 @@ void __declspec(naked) op_perk_add_mode() { void __declspec(naked) op_remove_trait() { __asm { - pushad; - mov ebp, eax; - call fo::funcoffs::interpretPopShort_; - mov edi, eax; - mov eax, ebp; - call fo::funcoffs::interpretPopLong_; - cmp di, VAR_TYPE_INT; - jnz end; - xor ebx, ebx; - dec ebx; - mov ecx, ds:[FO_VAR_pc_trait + 4]; - cmp eax, ds:[FO_VAR_pc_trait]; - jne next; - mov ds : [FO_VAR_pc_trait], ecx; - mov ds : [FO_VAR_pc_trait + 4], ebx; - jmp end; -next: - cmp eax, ds : [FO_VAR_pc_trait + 4]; - jne end; - mov ds : [FO_VAR_pc_trait + 4], ebx; + push ecx; + _GET_ARG_INT(end); + test eax, eax; + jl end; + mov edx, -1; + cmp eax, ds:[FO_VAR_pc_trait]; + jne next; + mov ecx, ds:[FO_VAR_pc_trait2]; + mov ds:[FO_VAR_pc_trait], ecx; + mov ds:[FO_VAR_pc_trait2], edx; end: - popad; + pop ecx; + retn; +next: + cmp eax, ds:[FO_VAR_pc_trait2]; + jne end; + mov ds:[FO_VAR_pc_trait2], edx; + pop ecx; retn; } } diff --git a/sfall/Modules/Scripting/Handlers/Perks.h b/sfall/Modules/Scripting/Handlers/Perks.h index ef7a693ac..1e2d6c84d 100644 --- a/sfall/Modules/Scripting/Handlers/Perks.h +++ b/sfall/Modules/Scripting/Handlers/Perks.h @@ -37,11 +37,17 @@ void __declspec() op_set_perk_desc(); void __declspec() op_set_perk_value(); -void __declspec() op_set_selectable_perk(); +void sf_set_selectable_perk(OpcodeContext&); -void __declspec() op_set_fake_perk(); +void sf_set_fake_perk(OpcodeContext&); -void __declspec() op_set_fake_trait(); +void sf_set_fake_trait(OpcodeContext&); + +void sf_set_selectable_perk_npc(OpcodeContext&); + +void sf_set_fake_perk_npc(OpcodeContext&); + +void sf_set_fake_trait_npc(OpcodeContext&); void __declspec() op_set_perkbox_title(); @@ -53,7 +59,11 @@ void __declspec() op_clear_selectable_perks(); void sf_has_fake_perk(OpcodeContext&); -void __declspec() op_has_fake_trait(); +void sf_has_fake_trait(OpcodeContext&); + +void sf_has_fake_perk_npc(OpcodeContext&); + +void sf_has_fake_trait_npc(OpcodeContext&); void __declspec() op_perk_add_mode(); diff --git a/sfall/Modules/Scripting/Handlers/Stats.cpp b/sfall/Modules/Scripting/Handlers/Stats.cpp index 7bf90202b..de4ead4cc 100644 --- a/sfall/Modules/Scripting/Handlers/Stats.cpp +++ b/sfall/Modules/Scripting/Handlers/Stats.cpp @@ -31,15 +31,15 @@ namespace sfall namespace script { -const char* invalidStat = "stat number out of range"; -const char* objNotCritter = "the object is not a critter"; +const char* invalidStat = "%s() - stat number out of range."; +const char* objNotCritter = "%s() - the object is not a critter."; void sf_set_pc_base_stat(OpcodeContext& ctx) { int stat = ctx.arg(0).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { ((long*)FO_VAR_pc_proto)[9 + stat] = ctx.arg(1).rawValue(); } else { - ctx.printOpcodeError("set_pc_base_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } } @@ -48,7 +48,7 @@ void sf_set_pc_extra_stat(OpcodeContext& ctx) { if (stat >= 0 && stat < fo::STAT_max_stat) { ((long*)FO_VAR_pc_proto)[44 + stat] = ctx.arg(1).rawValue(); } else { - ctx.printOpcodeError("set_pc_extra_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } } @@ -58,7 +58,7 @@ void sf_get_pc_base_stat(OpcodeContext& ctx) { if (stat >= 0 && stat < fo::STAT_max_stat) { value = ((long*)FO_VAR_pc_proto)[9 + stat]; } else { - ctx.printOpcodeError("get_pc_base_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } ctx.setReturn(value); } @@ -69,7 +69,7 @@ void sf_get_pc_extra_stat(OpcodeContext& ctx) { if (stat >= 0 && stat < fo::STAT_max_stat) { value = ((long*)FO_VAR_pc_proto)[44 + stat]; } else { - ctx.printOpcodeError("get_pc_extra_stat() - %s.", invalidStat); + ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } ctx.setReturn(value); } @@ -80,7 +80,7 @@ void sf_set_critter_base_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) Stats::SetStat(obj, stat, ctx.arg(2).rawValue(), 9); } else { - ctx.printOpcodeError("set_critter_base_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } } @@ -90,7 +90,7 @@ void sf_set_critter_extra_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) Stats::SetStat(obj, stat, ctx.arg(2).rawValue(), 44); } else { - ctx.printOpcodeError("set_critter_extra_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } } @@ -101,7 +101,7 @@ void sf_get_critter_base_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) result = Stats::GetStat(obj, stat, 9); } else { - ctx.printOpcodeError("get_critter_base_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } ctx.setReturn(result); } @@ -113,7 +113,7 @@ void sf_get_critter_extra_stat(OpcodeContext& ctx) { int stat = ctx.arg(1).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) result = Stats::GetStat(obj, stat, 44); } else { - ctx.printOpcodeError("get_critter_extra_stat() - %s.", objNotCritter); + ctx.printOpcodeError(objNotCritter, ctx.getOpcodeName()); } ctx.setReturn(result); } @@ -159,7 +159,7 @@ void __declspec(naked) op_set_critter_skill_points() { mov edx, esp; call fo::funcoffs::proto_ptr_; mov eax, [esp]; - mov[eax + 0x13c + esi * 4], edi; + mov [eax + 0x13c + esi * 4], edi; end: //Restore registers and return add esp, 12; @@ -323,7 +323,7 @@ void __declspec(naked) op_set_critter_current_ap() { jnz end; cmp si, VAR_TYPE_INT; jnz end; - mov[eax + 0x40], ebx; + mov [eax + 0x40], ebx; mov ecx, ds:[FO_VAR_obj_dude] cmp ecx, eax; jne end; @@ -899,7 +899,7 @@ static void __declspec(naked) SetPerkLevelMod3() { } static void _stdcall SetPerkLevelMod2(int mod) { - if (mod < -25 || mod>25) return; + if (mod < -25 || mod > 25) return; PerkLevelMod = mod; HookCall(0x49687F, &SetPerkLevelMod3); } diff --git a/sfall/Modules/Scripting/Handlers/Worldmap.cpp b/sfall/Modules/Scripting/Handlers/Worldmap.cpp index 9d18fd2df..17024b872 100644 --- a/sfall/Modules/Scripting/Handlers/Worldmap.cpp +++ b/sfall/Modules/Scripting/Handlers/Worldmap.cpp @@ -71,8 +71,8 @@ void __declspec(naked) op_force_encounter() { mov ecx, eax; // mapID call ForceEncounter; end: - pop edx; - pop ecx; + pop edx; + pop ecx; retn; } } @@ -153,8 +153,8 @@ void __declspec(naked) op_get_world_map_y_pos() { push ecx; mov edx, ds:[FO_VAR_world_ypos]; _RET_VAL_INT(ecx); - pop ecx; - pop edx; + pop ecx; + pop edx; retn; } } @@ -181,8 +181,8 @@ void __declspec(naked) op_set_world_map_pos() { jnz end; cmp si, VAR_TYPE_INT; jnz end; - mov ds : [FO_VAR_world_xpos], eax; - mov ds : [FO_VAR_world_ypos], edi; + mov ds:[FO_VAR_world_xpos], eax; + mov ds:[FO_VAR_world_ypos], edi; end: pop esi; pop edi; @@ -262,12 +262,12 @@ void sf_set_rest_mode(OpcodeContext& ctx) { void sf_set_rest_on_map(OpcodeContext& ctx) { long mapId = ctx.arg(0).asInt(); if (mapId < 0) { - ctx.printOpcodeError("set_can_rest_on_map() - invalid map number argument."); + ctx.printOpcodeError("%s() - invalid map number argument.", ctx.getMetaruleName()); return; } long elev = ctx.arg(1).asInt(); if (elev < -1 && elev > 2) { - ctx.printOpcodeError("set_can_rest_on_map() - invalid map elevation argument."); + ctx.printOpcodeError("%s() - invalid map elevation argument.", ctx.getMetaruleName()); } else { Worldmap::SetRestMapLevel(mapId, elev, ctx.arg(2).asBool()); } @@ -276,7 +276,7 @@ void sf_set_rest_on_map(OpcodeContext& ctx) { void sf_get_rest_on_map(OpcodeContext& ctx) { long elev = ctx.arg(1).asInt(); if (elev < 0 && elev > 2) { - ctx.printOpcodeError("get_can_rest_on_map() - invalid map elevation argument."); + ctx.printOpcodeError("%s() - invalid map elevation argument.", ctx.getMetaruleName()); } else { ctx.setReturn(Worldmap::GetRestMapLevel(elev, ctx.arg(0).asInt())); } diff --git a/sfall/Modules/Scripting/OpcodeContext.cpp b/sfall/Modules/Scripting/OpcodeContext.cpp index e5ea83a38..4ee830b06 100644 --- a/sfall/Modules/Scripting/OpcodeContext.cpp +++ b/sfall/Modules/Scripting/OpcodeContext.cpp @@ -27,15 +27,24 @@ namespace sfall namespace script { -OpcodeContext::OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn) { +OpcodeContext::OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn) + : _program(program), _opcode(opcode), _numArgs(argNum), _hasReturn(hasReturn), _argShift(0) +{ assert(argNum < OP_MAX_ARGUMENTS); +} + +OpcodeContext::OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn, const char* opcodeName) + : OpcodeContext::OpcodeContext(program, opcode, argNum, hasReturn) +{ + _opcodeName = opcodeName; +} - _program = program; - _opcode = opcode; +const char* OpcodeContext::getOpcodeName() const { + return _opcodeName; +} - _numArgs = argNum; - _hasReturn = hasReturn; - _argShift = 0; +const char* OpcodeContext::getMetaruleName() const { + return metarule->name; } int OpcodeContext::numArgs() const { @@ -57,7 +66,8 @@ void OpcodeContext::setArgShift (int shift) { } const ScriptValue& OpcodeContext::arg(int index) const { - return _args.at(index + _argShift); + assert((index + _argShift) < OP_MAX_ARGUMENTS); + return _args[index + _argShift]; } const ScriptValue& OpcodeContext::returnValue() const { @@ -95,11 +105,12 @@ void OpcodeContext::printOpcodeError(const char* fmt, ...) const { bool OpcodeContext::validateArguments(const OpcodeArgumentType argTypes[], const char* opcodeName) const { for (int i = 0; i < _numArgs; i++) { - auto argType = argTypes[i]; auto actualType = arg(i).type(); // display invalid type error if type is set and differs from actual type // exception is when type set to if (actualType == DataType::NONE) break; + auto argType = argTypes[i]; + if (argType == ARG_ANY) continue; if ((argType == ARG_INT || argType == ARG_OBJECT) && !(actualType == DataType::INT)) { printOpcodeError("%s() - argument #%d is not an integer.", opcodeName, ++i); return false; @@ -121,22 +132,23 @@ bool OpcodeContext::validateArguments(const OpcodeArgumentType argTypes[], const } void OpcodeContext::handleOpcode(ScriptingFunctionHandler func) { - _popArguments(); + if (_numArgs) _popArguments(); func(*this); - _pushReturnValue(); + if (_hasReturn) _pushReturnValue(); } -void OpcodeContext::handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[], const char* opcodeName) { - _popArguments(); +void OpcodeContext::handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[]) { + if (_numArgs) _popArguments(); - if (validateArguments(argTypes, opcodeName)) { + if (!_numArgs || validateArguments(argTypes, _opcodeName)) { func(*this); } else if (_hasReturn) { setReturn(-1); // is a common practice to return -1 in case of errors in fallout engine } - _pushReturnValue(); + + if (_hasReturn) _pushReturnValue(); } void __stdcall OpcodeContext::handleOpcodeStatic(fo::Program* program, DWORD opcodeOffset, ScriptingFunctionHandler func, char argNum, bool hasReturn) { @@ -198,25 +210,23 @@ void OpcodeContext::_popArguments() { // retrieve string argument if (type == DataType::STR) { - _args.at(i) = fo::func::interpretGetString(_program, rawValueType, rawValue); + _args[i] = fo::func::interpretGetString(_program, rawValueType, rawValue); } else { - _args.at(i) = ScriptValue(type, rawValue); + _args[i] = ScriptValue(type, rawValue); } } } void OpcodeContext::_pushReturnValue() { - if (_hasReturn) { - if (_ret.type() == DataType::NONE) { - _ret = ScriptValue(0); // if no value was set in handler, force return 0 to avoid stack error - } - DWORD rawResult = _ret.rawValue(); - if (_ret.type() == DataType::STR) { - rawResult = fo::func::interpretAddString(_program, _ret.strValue()); - } - fo::func::interpretPushLong(_program, rawResult); - fo::func::interpretPushShort(_program, getScriptTypeBySfallType(_ret.type())); + if (_ret.type() == DataType::NONE) { + _ret = ScriptValue(0); // if no value was set in handler, force return 0 to avoid stack error + } + DWORD rawResult = _ret.rawValue(); + if (_ret.type() == DataType::STR) { + rawResult = fo::func::interpretAddString(_program, _ret.strValue()); } + fo::func::interpretPushLong(_program, rawResult); + fo::func::interpretPushShort(_program, getScriptTypeBySfallType(_ret.type())); } } diff --git a/sfall/Modules/Scripting/OpcodeContext.h b/sfall/Modules/Scripting/OpcodeContext.h index 48067b546..69e95eb38 100644 --- a/sfall/Modules/Scripting/OpcodeContext.h +++ b/sfall/Modules/Scripting/OpcodeContext.h @@ -66,6 +66,23 @@ typedef struct SfallOpcodeInfo { } SfallOpcodeInfo; +typedef struct SfallMetarule { + // function name + const char* name; + + // pointer to handler function + ScriptingFunctionHandler func; + + // minimum number of arguments + short minArgs; + + // maximum number of arguments + short maxArgs; + + // argument validation settings + OpcodeArgumentType argValidation[OP_MAX_ARGUMENTS]; +} SfallMetarule; + // A context for handling opcodes. Opcode handlers can retrieve arguments and set opcode return value via context. class OpcodeContext { public: @@ -73,7 +90,15 @@ class OpcodeContext { // opcode - opcode number // argNum - number of arguments for this opcode // hasReturn - true if opcode has return value (is expression) + // opcodeName - name of a function (for logging) OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn); + OpcodeContext(fo::Program* program, DWORD opcode, int argNum, bool hasReturn, const char* opcodeName); + + const char* getOpcodeName() const; + const char* getMetaruleName() const; + + // currently executed metarule func + const SfallMetarule* metarule; // number of arguments, possibly reduced by argShift int numArgs() const; @@ -121,7 +146,7 @@ class OpcodeContext { // func - opcode handler // argTypes - argument types for validation // opcodeName - name of a function (for logging) - void handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[], const char* opcodeName); + void handleOpcode(ScriptingFunctionHandler func, const OpcodeArgumentType argTypes[]); // handles opcode using default instance static void __stdcall handleOpcodeStatic(fo::Program* program, DWORD opcodeOffset, ScriptingFunctionHandler func, char argNum, bool hasReturn); @@ -141,6 +166,8 @@ class OpcodeContext { fo::Program* _program; DWORD _opcode; + const char* _opcodeName; + int _numArgs; bool _hasReturn; int _argShift; diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index ce1c2953a..673123137 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -57,7 +57,7 @@ typedef std::unordered_map OpcodeInfoMapType; // opcode number, // function name, // function handler, -// number of arguments, +// number of arguments (max 7), // has return value, // { argument 1 type, argument 2 type, ...} // } @@ -84,8 +84,13 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x19e, "get_sfall_global_int", sf_get_sfall_global_int, 1, true, {ARG_INTSTR}}, {0x19f, "get_sfall_global_float", sf_get_sfall_global_float, 1, true, {ARG_INTSTR}}, {0x1a5, "inc_npc_level", sf_inc_npc_level, 1, false, {ARG_INTSTR}}, + {0x1ac, "get_ini_setting", sf_get_ini_setting, 1, true, {ARG_STRING}}, + {0x1bb, "set_fake_perk", sf_set_fake_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {0x1bc, "set_fake_trait", sf_set_fake_trait, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, + {0x1bd, "set_selectable_perk", sf_set_selectable_perk, 4, false, {ARG_STRING, ARG_INT, ARG_INT, ARG_STRING}}, {0x1c1, "has_fake_perk", sf_has_fake_perk, 1, true, {ARG_INTSTR}}, + {0x1c2, "has_fake_trait", sf_has_fake_trait, 1, true, {ARG_STRING}}, {0x1dc, "show_iface_tag", sf_show_iface_tag, 1, false, {ARG_INT}}, {0x1dd, "hide_iface_tag", sf_hide_iface_tag, 1, false, {ARG_INT}}, @@ -93,6 +98,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x1e1, "set_critical_table", sf_set_critical_table, 5, false, {ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {0x1e2, "get_critical_table", sf_get_critical_table, 4, true, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {0x1e3, "reset_critical_table", sf_reset_critical_table, 4, false, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, + {0x1eb, "get_ini_string", sf_get_ini_string, 1, true, {ARG_STRING}}, {0x1ec, "sqrt", sf_sqrt, 1, true, {ARG_NUMBER}}, {0x1ed, "abs", sf_abs, 1, true, {ARG_NUMBER}}, {0x1ee, "sin", sf_sin, 1, true, {ARG_NUMBER}}, @@ -135,6 +141,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x21a, "set_weapon_ammo_count", sf_set_weapon_ammo_count, 2, false, {ARG_OBJECT, ARG_INT}}, {0x21e, "get_mouse_buttons", sf_get_mouse_buttons, 0, true}, + {0x224, "create_message_window", sf_create_message_window, 1, false, {ARG_STRING}}, {0x22d, "create_array", sf_create_array, 2, true, {ARG_INT, ARG_INT}}, {0x22e, "set_array", sf_set_array, 3, false, {ARG_OBJECT, ARG_ANY, ARG_ANY}}, {0x22f, "get_array", sf_get_array, 2, true, {ARG_ANY, ARG_ANY}}, // can also be used on strings @@ -198,7 +205,7 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x279, "sfall_func3", HandleMetarule, 4, true}, {0x27a, "sfall_func4", HandleMetarule, 5, true}, {0x27b, "sfall_func5", HandleMetarule, 6, true}, - {0x27c, "sfall_func6", HandleMetarule, 7, true}, // if you need more arguments - use arrays + {0x27c, "sfall_func6", HandleMetarule, 7, true}, // if you need more arguments - use arrays }; // A hash-table for opcode info, indexed by opcode. @@ -221,8 +228,8 @@ void __fastcall defaultOpcodeHandlerCall(fo::Program* program, DWORD opcodeOffse auto iter = opcodeInfoMap.find(opcode); if (iter != opcodeInfoMap.end()) { auto info = iter->second; - OpcodeContext ctx(program, opcode, info->argNum, info->hasReturn); - ctx.handleOpcode(info->handler, info->argValidation, info->name); + OpcodeContext ctx(program, opcode, info->argNum, info->hasReturn, info->name); + ctx.handleOpcode(info->handler, info->argValidation); } else { fo::func::interpretError("Unknown opcode: %d", opcode); } @@ -313,7 +320,6 @@ void InitNewOpcodes() { opcodes[0x1a9] = op_set_viewport_y; opcodes[0x1aa] = op_set_xp_mod; opcodes[0x1ab] = op_set_perk_level_mod; - opcodes[0x1ac] = op_get_ini_setting; opcodes[0x1ad] = op_get_shader_version; opcodes[0x1ae] = op_set_shader_mode; opcodes[0x1af] = op_get_game_mode; @@ -328,13 +334,9 @@ void InitNewOpcodes() { opcodes[0x1b8] = op_set_pc_stat_min; opcodes[0x1b9] = op_set_npc_stat_max; opcodes[0x1ba] = op_set_npc_stat_min; - opcodes[0x1bb] = op_set_fake_perk; - opcodes[0x1bc] = op_set_fake_trait; - opcodes[0x1bd] = op_set_selectable_perk; opcodes[0x1be] = op_set_perkbox_title; opcodes[0x1bf] = op_hide_real_perks; opcodes[0x1c0] = op_show_real_perks; - opcodes[0x1c2] = op_has_fake_trait; opcodes[0x1c3] = op_perk_add_mode; opcodes[0x1c4] = op_clear_selectable_perks; opcodes[0x1c5] = op_set_critter_hit_chance_mod; @@ -364,7 +366,6 @@ void InitNewOpcodes() { opcodes[0x1e8] = op_set_unspent_ap_perk_bonus; opcodes[0x1e9] = op_get_unspent_ap_perk_bonus; opcodes[0x1ea] = op_init_hook; - opcodes[0x1eb] = op_get_ini_string; opcodes[0x1f2] = op_set_palette; opcodes[0x1f3] = op_remove_script; opcodes[0x1f4] = op_set_script; @@ -384,7 +385,6 @@ void InitNewOpcodes() { opcodes[0x221] = op_get_screen_height; opcodes[0x222] = op_stop_game; opcodes[0x223] = op_resume_game; - opcodes[0x224] = op_create_message_window; opcodes[0x225] = op_remove_trait; opcodes[0x226] = op_get_light_level; opcodes[0x227] = op_refresh_pc_art; diff --git a/sfall/Modules/Sound.cpp b/sfall/Modules/Sound.cpp index 5ec64e896..fe7f57a78 100644 --- a/sfall/Modules/Sound.cpp +++ b/sfall/Modules/Sound.cpp @@ -64,7 +64,7 @@ void Sound::init() { HookCall(0x42B849, ai_print_msg_hook); //Yes, I did leave this in on purpose. Will be of use to anyone trying to add in the sound effects - if (isDebug && GetConfigInt("Sound", "Test_ForceFloats", 0)) { + if (isDebug && GetPrivateProfileIntA("Debugging", "Test_ForceFloats", 0, ::sfall::ddrawIni)) { SafeWrite8(0x42B6F5, 0xEB); // bypass chance } } diff --git a/sfall/Modules/Stats.cpp b/sfall/Modules/Stats.cpp index 99115c58c..84cf7d7ab 100644 --- a/sfall/Modules/Stats.cpp +++ b/sfall/Modules/Stats.cpp @@ -255,19 +255,11 @@ static void SetStatValue(long* proto, long offset, long amount) { proto[offset] = amount; } -static long CopyProto(long pid, long* &proto_out) { - fo::Proto* _proto; - if (fo::func::proto_ptr(pid, &_proto) == -1) return 0; - proto_out = new long[104]; - memcpy(proto_out, _proto, 416); - return 1; -} - static long ApplyAllStatsToProto(const protoMem_iterator &iter, long* &proto_out) { long count = 0; for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { if (itBonus->objID == iter->first && itBonus->objPID == iter->second.pid) { - if (!proto_out && !CopyProto(iter->second.pid, proto_out)) return 0; + if (!proto_out && !fo::CritterCopyProto(iter->second.pid, proto_out)) return 0; itBonus->s_proto = proto_out; SetStatValue(proto_out, 44 + itBonus->stat, itBonus->amount); count++; @@ -275,7 +267,7 @@ static long ApplyAllStatsToProto(const protoMem_iterator &iter, long* &proto_out } for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { if (itBase->objID == iter->first && itBase->objPID == iter->second.pid) { - if (!proto_out && !CopyProto(iter->second.pid, proto_out)) return 0; + if (!proto_out && !fo::CritterCopyProto(iter->second.pid, proto_out)) return 0; itBase->s_proto = proto_out; SetStatValue(proto_out, 9 + itBase->stat, itBase->amount); count++; @@ -696,4 +688,4 @@ void _stdcall SetNPCStatMin(int stat, int i) { } } -} \ No newline at end of file +} diff --git a/sfall/Modules/TalkingHeads.cpp b/sfall/Modules/TalkingHeads.cpp index df83cd247..10f480f83 100644 --- a/sfall/Modules/TalkingHeads.cpp +++ b/sfall/Modules/TalkingHeads.cpp @@ -29,8 +29,6 @@ namespace sfall { -extern IDirect3DDevice9* d3d9Device; - #pragma pack(push, 1) struct Frm { DWORD version; @@ -155,7 +153,7 @@ static bool LoadFrm(Frm* frm) { } // make mask image for (int i = 0; i < frm->frames; i++) { - fo::FrmSubframeData* frame = fo::func::frame_ptr((fo::FrmFrameData*)frm, i, 0); + fo::FrmFrameData* frame = fo::func::frame_ptr((fo::FrmHeaderData*)frm, i, 0); if (frm->bakedBackground) { memset(frame->data, 255, frame->size); } else { @@ -171,7 +169,7 @@ static bool LoadFrm(Frm* frm) { static void __fastcall DrawHeadFrame(Frm* frm, int frameno) { if (frm && !frm->broken) { if (!frm->loaded && !LoadFrm(frm)) goto loadFail; - fo::FrmSubframeData* frame = fo::func::frame_ptr((fo::FrmFrameData*)frm, frameno, 0); + fo::FrmFrameData* frame = fo::func::frame_ptr((fo::FrmHeaderData*)frm, frameno, 0); Graphics::SetHeadTex(frm->textures[frameno], frame->width, frame->height, frame->x + frm->xshift, frame->y + frm->yshift, (frm->showHighlights == 2)); showHighlights = frm->showHighlights; return; diff --git a/sfall/Modules/Tiles.cpp b/sfall/Modules/Tiles.cpp index 484d3da01..b246099b2 100644 --- a/sfall/Modules/Tiles.cpp +++ b/sfall/Modules/Tiles.cpp @@ -29,6 +29,9 @@ namespace sfall { using namespace fo; +typedef int (_stdcall *functype)(); +static const functype art_init = (functype)fo::funcoffs::art_init_; + static const DWORD Tiles_0E[] = { 0x484255, 0x48429D, 0x484377, 0x484385, 0x48A897, 0x48A89A, 0x4B2231, 0x4B2374, 0x4B2381, 0x4B2480, 0x4B248D, 0x4B2A7C, 0x4B2BDA, @@ -50,40 +53,39 @@ static const DWORD Tiles_C0[] = { 0x4B247B, 0x4B2A77, 0x4B2BD5, }; +struct tilestruct { + short tile[2]; +}; + struct OverrideEntry { - //DWORD id; DWORD xtiles; DWORD ytiles; DWORD replacementid; - OverrideEntry(DWORD _xtiles, DWORD _ytiles, DWORD _repid) { - xtiles = _xtiles; - ytiles = _ytiles; - replacementid = _repid; + OverrideEntry(DWORD _xtiles, DWORD _ytiles, DWORD _repid) + : xtiles(_xtiles), ytiles(_ytiles), replacementid(_repid) { } }; static OverrideEntry** overrides; static DWORD origTileCount = 0; - -typedef int (_stdcall *functype)(); -static const functype _art_init = (functype)fo::funcoffs::art_init_; +static DWORD tileMode; static BYTE* mask; static void CreateMask() { - mask = new BYTE[80*36]; + mask = new BYTE[80 * 36]; fo::DbFile* file = fo::func::db_fopen("art\\tiles\\grid000.frm", "r"); - fo::func::db_fseek(file, 0x4a, 0); - fo::func::db_freadByteCount(file, mask, 80*36); + fo::func::db_fseek(file, 0x4A, 0); + fo::func::db_freadByteCount(file, mask, 80 * 36); fo::func::db_fclose(file); } static WORD ByteSwapW(WORD w) { - return ((w & 0xff) << 8) | ((w & 0xff00) >> 8); + return ((w & 0xFF) << 8) | ((w & 0xFF00) >> 8); } static DWORD ByteSwapD(DWORD w) { - return ((w & 0xff) << 24) | ((w & 0xff00) << 8) | ((w & 0xff0000) >> 8) | ((w & 0xff000000) >> 24); + return ((w & 0xFF) << 24) | ((w & 0xFF00) << 8) | ((w & 0xFF0000) >> 8) | ((w & 0xFF000000) >> 24); } static int ProcessTile(fo::Art* tiles, int tile, int listpos) { @@ -94,7 +96,7 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { fo::DbFile* art = fo::func::db_fopen(buf, "r"); if (!art) return 0; - fo::func::db_fseek(art, 0x3e, 0); + fo::func::db_fseek(art, 0x3E, 0); WORD width; fo::func::db_freadShort(art, &width); //80; if (width == 80) { @@ -118,8 +120,8 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { fo::func::db_freadByteCount(art, (BYTE*)&frame, 0x4a); frame.height = ByteSwapW(36); frame.width = ByteSwapW(80); - frame.frmSize = ByteSwapD(80 * 36); - frame.size = ByteSwapD(80 * 36 + 12); + frame.frameSize = ByteSwapD(80 * 36); + frame.frameAreaSize = ByteSwapD(80 * 36 + 12); int xoffset = x * 48 + (ysize - (y + 1)) * 32; int yoffset = height - (36 + x * 12 + y * 24); for (int y2 = 0; y2 < 36; y2++) { @@ -145,11 +147,8 @@ static int ProcessTile(fo::Art* tiles, int tile, int listpos) { return xsize * ysize; } -static DWORD tileMode; -static int _stdcall ArtInitHook2() { - if (_art_init()) { - return 1; - } +static int _stdcall ArtInitHook() { + if (art_init()) return -1; CreateMask(); @@ -186,27 +185,18 @@ static int _stdcall ArtInitHook2() { return 0; } -static void __declspec(naked) ArtInitHook() { +static void __declspec(naked) iso_init_hook() { __asm { - pushad; - mov eax, dword ptr ds:[FO_VAR_read_callback]; - push eax; - xor eax, eax; - mov dword ptr ds:[FO_VAR_read_callback], eax; - call ArtInitHook2; - pop eax; - mov dword ptr ds:[FO_VAR_read_callback], eax; - popad; - xor eax, eax; + mov ebx, dword ptr ds:[FO_VAR_read_callback]; + xor eax, eax; + mov dword ptr ds:[FO_VAR_read_callback], eax; + call ArtInitHook; + mov dword ptr ds:[FO_VAR_read_callback], ebx; retn; } } -struct tilestruct { - short tile[2]; -}; - -static void _stdcall SquareLoadCheck(tilestruct* data) { +static void __fastcall SquareLoadCheck(tilestruct* data) { for (DWORD y = 0; y < 100; y++) { for (DWORD x = 0; x < 100; x++) { for (DWORD z = 0; z < 2; z++) { @@ -226,16 +216,13 @@ static void _stdcall SquareLoadCheck(tilestruct* data) { } } -static void __declspec(naked) SquareLoadHook() { +static void __declspec(naked) square_load_hook() { __asm { - mov edi, edx; + mov ecx, edx; call fo::funcoffs::db_freadIntCount_; test eax, eax; - jnz end; - pushad; - push edi; - call SquareLoadCheck; - popad; + jnz end; + jmp SquareLoadCheck; end: retn; } @@ -270,11 +257,10 @@ static void __declspec(naked) art_get_name_hack() { } void Tiles::init() { - tileMode = GetConfigInt("Misc", "AllowLargeTiles", 0); - if (tileMode) { + if (tileMode = GetConfigInt("Misc", "AllowLargeTiles", 0)) { dlog("Applying allow large tiles patch.", DL_INIT); - HookCall(0x481D72, &ArtInitHook); - HookCall(0x48434C, SquareLoadHook); + HookCall(0x481D72, iso_init_hook); + HookCall(0x48434C, square_load_hook); dlogr(" Done", DL_INIT); } diff --git a/sfall/Modules/Worldmap.cpp b/sfall/Modules/Worldmap.cpp index ba824bc9f..3cc33fc7e 100644 --- a/sfall/Modules/Worldmap.cpp +++ b/sfall/Modules/Worldmap.cpp @@ -478,7 +478,7 @@ void PathfinderFixInit() { void StartingStatePatches() { int date = GetConfigInt("Misc", "StartYear", -1); - if (date > 0) { + if (date >= 0) { dlog("Applying starting year patch.", DL_INIT); SafeWrite32(0x4A336C, date); dlogr(" Done", DL_INIT); @@ -635,14 +635,13 @@ void Worldmap::SetRestMapLevel(int mapId, long elev, bool canRest) { } levelRest elevData = {-1, -1, -1, -1}; - std::pair info (mapId, elevData); if (elev == -1) { - info.second.level[++elev] = canRest; - info.second.level[++elev] = canRest; + elevData.level[++elev] = canRest; + elevData.level[++elev] = canRest; elev++; } - info.second.level[elev] = canRest; - mapRestInfo.insert(info); + elevData.level[elev] = canRest; + mapRestInfo.insert(std::make_pair(mapId, elevData)); } } diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 34b97c585..8be8f4786 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -341,6 +341,7 @@ + @@ -462,6 +463,7 @@ + Create Create diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index cf5b80dd5..03d437a68 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -278,6 +278,9 @@ Modules + + Modules + @@ -516,6 +519,9 @@ Modules + + Modules + diff --git a/sfall/main.cpp b/sfall/main.cpp index 0f85697e1..22b384ce7 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -63,6 +63,7 @@ #include "Modules\QuestList.h" #include "Modules\Reputations.h" #include "Modules\ScriptExtender.h" +#include "Modules\ScriptShaders.h" #include "Modules\Skills.h" #include "Modules\Sound.h" #include "Modules\SpeedPatch.h" @@ -138,16 +139,14 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); + manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); manager.add(); - manager.add(); - manager.add(); - manager.add(); - manager.add(); manager.add(); manager.add(); manager.add(); @@ -180,6 +179,12 @@ static void InitModules() { manager.add(); manager.add(); manager.add(); + manager.add(); + + // all built-in events(delegates) of modules should be executed before running the script handlers + manager.add(); + manager.add(); + manager.add(); manager.initAll(); diff --git a/sfall/version.h b/sfall/version.h index 58b131584..13870dd9c 100644 --- a/sfall/version.h +++ b/sfall/version.h @@ -24,10 +24,10 @@ #define VERSION_MAJOR 4 #define VERSION_MINOR 1 -#define VERSION_BUILD 7 +#define VERSION_BUILD 8 #define VERSION_REV 0 -#define VERSION_STRING "4.1.7" +#define VERSION_STRING "4.1.8" //#define CHECK_VAL (4)