diff --git a/.gitignore b/.gitignore index 76671817e..6597f9a99 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ *.exe *.out *.app +!ducible.exe # NetBeans nbproject/private/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ba8c518cf..753ccd03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## 4.4 +* Implemented a `mods_order.txt` to improve and simplify mod ordering and add support for mod managers. This replaces previous **.dat** file autoloading. Please refer to `ddraw.ini` for details +* Implemented a **custom config file parser**, which greatly improves the performance of sfall initialization and reading files from scripts +* **Item highlighting and NPC combat control mods** are now packed into `sfall.dat` resource file, and `sfall-mods.ini` is moved to `\mods\` +* Fixed a bug when updating the maximum HP stat of critters on the map when loaded for the first time +* Fixed `set_pc_base/extra_stat` and `set_critter_base/extra_stat` script functions not updating derived stats when setting SPECIAL stats +* Fixed `wield_obj_critter` script function, which can now put non-weapon/armor items in the critter's hand +* Fixed `get_tile_fid` script function to work on elevations 1 and 2 instead of always elevation 0 +* Fixed `create_spatial` script function not setting the script index number upon object creation +* Fixed incorrect behavior of the subtraction operator involving floats and negative integers +* Fixed the backward compatibility of `get_npc_level` script function +* Fixed a crash bug in `display_msg` and `debug_msg` script functions when printing a string longer than 260 characters +* Fixed a crash bug in `AMMOCOST` hook when returning ammo cost of 0 for burst attacks +* Fixed various issues in script compiler and decompiler (`compile.exe` and `int2ssl.exe` in the **modders pack**) +* Improved **item highlighting mod** to handle the line-of-sight check properly when the player is moving while holding down the key +* Improved and tweaked the behavior of **ComputeSpray_\*** options +* Expanded `atoi` script function to support parsing binary strings +* Expanded `string_format` script function to support more arguments and conversion types +* Changed **CheckWeaponAmmoCost** to be enabled by default and affect only hook type 1 of `HOOK_AMMOCOST` hook script +* Changed how `HOOK_DESCRIPTIONOBJ` hook script handles its return value. Now you can return normal strings directly in the hook +* Re-added the check for valid objects to `get/set_object_data` script functions (only disabled in combat for accessing the combat data) +* Removed the debug message about a missing critter art file from displaying in the game (added in 4.2.2) +* Added options to override the names of sound files used by the engine +* Added an option to **item highlighting mod** to highlight critters with the same rules as in combat, and changed **CheckLOS** to be enabled by default +* Added a debug option to duplicate logs to a dedicated console window alongside the game window +* Added a lower limit of -99% and an upper limit of 999% to the hit chance value to prevent a display issue +* Added more options for tweaking some engine perks to the **perks ini file** +* Added a new argument and a new return value to `HOOK_STEAL` hook script +* Added a burst control example script and a resting encounters mod to the example mods in the **modders pack** +* Updated the compute damage example script in the **modders pack**. Now it should be easier to write one's own damage formula +* Increased the setting range of the combat speed slider in the preferences screen +* New script functions: `set_spray_settings`, `get/set_combat_free_move`, `get_ini_config`, `string_find` + ## 4.3.8 * Fixed a bug introduced in 4.2.8 that broke the arguments of `HOOK_RESTTIMER` hook script * Fixed a bug introduced in 4.3.1 that caused the game to display incorrect names for corpses in some cases @@ -44,7 +77,7 @@ * HRP: Added support for **SPLASH_SCRN_TIME** option in `f2_res.ini` * Fixed the handling of obsolete script functions that are still recognized by script compiler and decompiler * Fixed **NPC combat control mod** not redrawing the interface bar properly when it's the player's turn again -* Improved the fix for updating the HP stats of critters on the map when loaded for the first time +* Improved the fix for updating the maximum HP stat of critters on the map when loaded for the first time * Removed **DivisionOperatorFix** from `ddraw.ini` because there is little reason to turn it off * Removed **ComputeSprayMod** from `ddraw.ini`. Now **ComputeSpray_\*** options no longer require a master switch * Added a fix for a crash when the player equips a weapon overloaded with ammo @@ -129,7 +162,7 @@ * Added missing sounds for the buttons on the world map interface (similar to Fallout 1 behavior) * Added 5 `metarule3` macros for controlling the save slot with scripts to `sfall.h` in the **modders pack** * New script functions: `set_scr_name`, `obj_is_openable` -* Updated **NPC armor appearance mod** to prevent NPCs from equipping **unusable** weapons +* Updated **NPC armor appearance mod** in the **modders pack** to prevent NPCs from equipping **unusable** weapons * Included Brazilian Portuguese and Polish translation (from Felipefpl and Jaiden) ## 4.3.0.2 @@ -212,7 +245,7 @@ * Added an option about the behavior of maximum HP calculation to the **stats ini file** * Added 3 new attribute type values to `get_window_attribute` script functions * Added additional universal opcodes `sfall_func7` and `sfall_func8` (`compile.exe` and `int2ssl.exe` in the **modders pack** are also updated) -* Added a new mode to **NPC combat control mod** to let you order party members to attack specified targets instead of controlling them directly +* Added a new mode to **NPC combat control mod** that lets you use the command cursor to specify targets for party members to attack in combat * Added an auto-close containers mod to the example mods in the **modders pack** * Added `snd2acm_fix.exe` (snd2acm with a fix wrapper) to the **modders pack** for writing the correct sample rate and channel info from **WAV** files to **ACM** format * New script functions: `interface_overlay` @@ -289,7 +322,7 @@ * Improved the pathfinding in the engine function when a multihex object is in the line of fire * Improved the functionality of `display_stats` script function to also update player's stats on the character screen * Improved the fix for incorrect positioning after exiting small/medium locations -* Removed **AutoSearchPath** from `ddraw.ini`. Now the folder path for autoloading custom files is the fixed **\\mods\\** +* Removed **AutoSearchPath** from `ddraw.ini`. Now the directory for autoloading custom files is the fixed `\mods\` * Added a fix to prevent critters from overlapping other object tiles when moving to the retargeted tile * Added a fix to prevent showing an empty perk selection window (crash when clicking on the empty perk list) * Added a fix for NPC stuck in an animation loop in combat when trying to move close to a multihex critter @@ -355,7 +388,7 @@ * Added a fix for the barter button on the dialog window not animating until after leaving the barter screen * Added a fix for the division operator treating negative integers as unsigned * Added a fix for trying to loot corpses with the **'NoSteal'** flag set -* Added an option to allow using the special `'^'` character in dialog msg files to specify the alternative text that will be displayed in the dialogue based on the player's gender +* Added an option to allow using the caret character `'^'` in dialog msg files to specify alternative text in dialogue based on the player's gender * Added options to draw a dotted line while traveling on the world map (similar to Fallout 1, from Ghosthack) * Added an option to display terrain types when hovering the cursor over the player's marker on the world map (from Ghosthack) * Added a flashing icon to the Horrigan encounter and scripted force encounters by default @@ -372,7 +405,7 @@ * Fixed and improved the functionality of `substr` script function * Restored and fixed **RemoveWindowRounding** option that was removed in 4.1.2 * Improved the functionality of `inventory_redraw` script function -* Changed the way **IniConfigFolder** works. Now the game will search for the ini files relative to the specified directory +* Changed the way **IniConfigFolder** works. Now the game will search for ini files relative to the specified directory * Changed the debug message about a missing critter art file to also be displayed in the game * Code refactoring for various script functions * Added a fix to prevent the player from moving when clicking on a script-created window and prevent the mouse cursor from being toggled when hovering over a **'hidden'** window @@ -488,7 +521,7 @@ * Added a fix for knocked down critters not playing stand up animation when the combat ends * Added a fix for dead NPCs reloading their weapons when the combat ends * Added an option to use the expanded world map interface (requires hi-res patch v4.1.8) -* Added an option to allow setting a folder path for the game to automatically search and load custom **.dat** files +* Added an option to allow setting a directory for the game to automatically search and load custom **.dat** files * Added an option to expand the number of action points displayed on the interface bar * Added an option to change the base value of the duration of the knockout effect * Added a check for the `DAM_KNOCKED_OUT` flag to `wield_obj_critter/inven_unwield` script functions @@ -511,7 +544,7 @@ * Changed `create_message_window` script function to be available when other game interfaces are opened * Removed **DialogOptions9Lines** from `ddraw.ini` because there is little reason to turn it off * Removed **LoadProtoMaxLimit** from `ddraw.ini`. Now the proto limit is automatically increased when needed -* Added a fix for party member's stats being reset to the base level when their base protos with the read-only attribute set are placed in the **proto\critters\\** directory +* Added a fix for party member's stats being reset to the base level when their base protos with the read-only attribute set are placed in the `proto\critters\` directory * Added a 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 * Added a fix to allow fleeing NPC to use drugs * Added a fix for AI not checking minimum HP properly for using stimpaks @@ -531,7 +564,7 @@ * Fixed fake perks not stacking properly * Fixed the position of the 32-bit talking heads when the game resolution is higher than 640x480 * Fixed a bug in **item highlighting** that caused items to be kept highlighted when picking them up while holding the highlight key -* Changed `hero_select_win` function to require an `AppHeroWin.frm` file (included in the **modders pack**) in the **art\intrface\\** directory +* Changed `hero_select_win` function to require an `AppHeroWin.frm` file (included in the **modders pack**) in the `art\intrface\` directory * Added a fix for unexplored areas being revealed on the automap when entering a map * Added a fix for the overflow of the automap tables when the number of maps in `maps.txt` is more than 160 * Added a fix for a duplicate `obj_dude` script being created when loading a saved game @@ -767,8 +800,8 @@ Original engine bug fixes and various features based on the work by Mr.Stalin: * The build environment is now **Visual Studio 2015**, and Win XP SP2 and Win 2000 are no longer supported * Extensive code reorganizing/rewrite was made in the effort to tidy up sfall code base accumulated over the years and make it easier to read, understand, and extend. Main code was split into separate **'modules'**. Code for interacting with Fallout 2 engine was moved and expanded to allow for engine manipulations without using too much Assembly code * Fixed an issue with the game being rendered before the **hero appearance mod** is loaded -* **Item highlighting mod** was moved from sfall into a separate script (`gl_highlighting.int`) and extended with new options -* **NPC combat control mod** was moved from sfall into a separate script (`gl_partycontrol.int`) +* **Item highlighting mod** is moved from sfall into a separate script (`gl_highlighting.int`) and extended with new options +* **NPC combat control mod** is moved from sfall into a separate script (`gl_partycontrol.int`) * Related options of **item highlighting** and **NPC combat control** mods were moved from `ddraw.ini` into a separate `sfall-mods.ini` * A new version of **NPC armor appearance mod** was implemented as a separate script (`gl_npcarmor.int` in the **modders pack**). It can be configured and made compatible with any Fallout 2 mod * Improved compatibility between **hero appearance**, **NPC combat control**, and **NPC armor appearance** mods diff --git a/README.md b/README.md index 716129ecc..73f1add22 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Original description: A set of engine modifications for the classic game Fallout - Download `sfall_*.7z` from the [release archive](https://sourceforge.net/projects/sfall/files/). -- Extract `ddraw.dll`, `ddraw.ini`, `sfall-mods.ini`, `sfall.dat`, and `data` folder to Fallout's base directory (i.e. the one that contains `fallout2.exe`). +- Extract `ddraw.dll`, `ddraw.ini`, `sfall.dat`, and the `mods` folder to Fallout's base directory (i.e. the one that contains `fallout2.exe`). Also, remove `gl_highlighting.int` and `gl_partycontrol.int` from Fallout's `data\scripts\` directory if you are updating from an older version. - __Important Note:__\ If you are using a mod that already included sfall (e.g. killap's [Unofficial Patch](https://github.com/BGforgeNet/Fallout2_Unofficial_Patch) or [Restoration Project](https://github.com/BGforgeNet/Fallout2_Restoration_Project)), then that mod has probably included a custom modified `ddraw.ini`. In that case, overwriting it with sfall's vanilla `ddraw.ini` will be likely break your game. Instead, only overwrite `ddraw.dll`, and keep the mod's existing copy of `ddraw.ini`. (Or, if you know what you're doing, you can merge them together by hand.) @@ -30,7 +30,7 @@ Original description: A set of engine modifications for the classic game Fallout ## Uninstallation -Delete `ddraw.dll`, `ddraw.ini`, `sfall-mods.ini`, and `sfall.dat` from your Fallout directory. Also, delete `gl_highlighting.int` and `gl_partycontrol.int` from Fallout's `data\scripts\` directory. +Delete `ddraw.dll`, `ddraw.ini`, and `sfall.dat` from your Fallout directory, and delete `sfall-mods.ini` from the `mods` folder. ## Usage @@ -42,6 +42,20 @@ In a default installation using an unmodified copy of `ddraw.ini`, the middle mo For [__Wine__](https://www.winehq.org/) users:\ You need to set DLL overrides for `ddraw.dll` to __"native, builtin"__ in `winecfg` or use `WINEDLLOVERRIDES="ddraw=n,b"` to run Fallout from the command line. If you want to play alternative sound files, you'll also need to install GStreamer Good 32-bit plugins. +## Build Instructions + +### Prerequisites: + +* Visual Studio 2015 with **Windows XP support for C++** component. If you're using Visual Studio 2017/2019/2022, make sure to install **VS 2015 C++ build tools (v140)**. +* [DirectX SDK (June 2010)](https://www.microsoft.com/en-us/download/details.aspx?id=6812). You will also need `ddraw.lib` from DXSDK February 2010 and `dinput.lib` from DXSDK August 2007. Both files can be found in the [DirectX SDK Collection repo](https://github.com/NovaRain/DXSDK_Collection). +* [DirectX Runtime (June 2010)](https://www.microsoft.com/en-us/download/details.aspx?id=8109). You can also install it from DirectX SDK installer. + +### Steps: + +1. Set up a `postbuild.cmd` using the template from the repo. +2. Open the solution file and build with the **ReleaseXP** configuration (this is the one used for sfall releases). +3. If everything is set up correctly, you should have a new sfall `ddraw.dll` in your Fallout 2 directory. + ## Additional info * [Changelog](CHANGELOG.md) diff --git a/action.yml b/action.yml index 66066e7fb..91275e25e 100644 --- a/action.yml +++ b/action.yml @@ -82,7 +82,7 @@ runs: # echo "::endgroup::" fi - echo "::set-output name=dir::$(cygpath --windows "$DXSDK_DIR/DXSDK_Jun2010/")" + echo "dir=$(cygpath --windows "$DXSDK_DIR/DXSDK_Jun2010/")" >> $GITHUB_OUTPUT shell: bash # MSBuild is not in PATH on Windows machines @@ -93,7 +93,7 @@ runs: echo "::group::Prepare MSBuild" MSBUILD_EXE="$("/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" -latest -requires Microsoft.Component.MSBuild -find MSBuild/**/Bin/MSBuild.exe)" - echo "::set-output name=exe::$MSBUILD_EXE" + echo "exe=$MSBUILD_EXE" >> $GITHUB_OUTPUT echo "::endgroup::" shell: bash @@ -106,10 +106,10 @@ runs: echo ::group::Build ReleaseXP # - echo "@echo off" > "$GITHUB_ACTION_PATH/sfall/PostBuild.cmd" + # echo "@echo off" > "$GITHUB_ACTION_PATH/sfall/PostBuild.cmd" "${{ steps.msbuild.outputs.exe }}" "$GITHUB_ACTION_PATH/sfall/ddraw.sln" -p:Configuration=ReleaseXP -p:Platform=Win32 -p:PlatformToolset=v140_xp # - echo "::set-output name=ddraw-dll::$(cygpath --windows "$GITHUB_ACTION_PATH/sfall/ReleaseXP/ddraw.dll")" + echo "ddraw-dll=$(cygpath --windows "$GITHUB_ACTION_PATH/sfall/ReleaseXP/ddraw.dll")" >> $GITHUB_OUTPUT echo "::endgroup::" fi @@ -126,11 +126,11 @@ runs: echo "::group::Build DevXP" # - echo "@echo off" > "$GITHUB_ACTION_PATH/sfall/PostBuild.cmd" + # echo "@echo off" > "$GITHUB_ACTION_PATH/sfall/PostBuild.cmd" "${{ steps.msbuild.outputs.exe }}" "$GITHUB_ACTION_PATH/sfall/ddraw.sln" -p:Configuration=DevXP -p:Platform=Win32 -p:PlatformToolset=v140_xp # - echo "::set-output name=ddraw-dll::$(cygpath --windows "$GITHUB_ACTION_PATH/sfall/DevXP/ddraw.dll")" - echo "::set-output name=ddraw-pdb::$(cygpath --windows "$GITHUB_ACTION_PATH/sfall/DevXP/ddraw.pdb")" + echo "ddraw-dll=$(cygpath --windows "$GITHUB_ACTION_PATH/sfall/DevXP/ddraw.dll")" >> $GITHUB_OUTPUT + echo "ddraw-pdb=$(cygpath --windows "$GITHUB_ACTION_PATH/sfall/DevXP/ddraw.pdb")" >> $GITHUB_OUTPUT echo "::endgroup::" fi diff --git a/artifacts/config_files/Perks.ini b/artifacts/config_files/Perks.ini index 0ff4861bd..2fa7d5234 100644 --- a/artifacts/config_files/Perks.ini +++ b/artifacts/config_files/Perks.ini @@ -7,10 +7,39 @@ ; be used to remove their hardcoded effects, and add new stat/skill effects [PerksTweak] +;Changes the Outdoorsman skill bonus for 'Survivalist' perk (ID 16) +;125 - maximum bonus, 25 - default bonus +SurvivalistBonus=25 + ;Changes the bonus for 'Master Trader' perk (ID 17) ;25 - default bonus MasterTraderBonus=25 +;Changes the Science and Repair skill bonus for 'Mr.Fixit' perk (ID 31) +;125 - maximum bonus, 10 - default bonus +MrFixitBonus=10 + +;Changes the skill bonuses for 'Medic' perk (ID 32) +;125 - maximum bonus, 10 - default bonus +MedicFirstAidBonus=10 +MedicDoctorBonus=10 + +;Changes the Lockpick and Steal skill bonus for 'Master Thief' perk (ID 33) +;125 - maximum bonus, 15 - default bonus +MasterThiefBonus=15 + +;Changes the Speech skill bonus for 'Speaker' perk (ID 34) +;125 - maximum bonus, 20 - default bonus +SpeakerBonus=20 + +;Changes the Sneak skill bonus (when in darkness) for 'Ghost' perk (ID 38) +;125 - maximum bonus, 20 - default bonus +GhostBonus=20 + +;Changes the Outdoorsman skill bonus for 'Ranger' perk (ID 47) +;125 - maximum bonus, 15 - default bonus +RangerOutdoorsmanBonus=15 + ;Changes the distance bonus for 'Weapon Long Range' weapon perk (ID 58) ;2 - no bonus, 4 - default bonus WeaponLongRangeBonus=4 @@ -34,9 +63,22 @@ VaultCityInoculationsRadBonus=10 ;999 - maximum bonus, 10 - default bonus DemolitionExpertBonus=10 -;Changes the damage bonus for 'Living Anatomy' perk (ID 97) -;125 - maximum bonus, 5 - default bonus +;Changes the Gambling skill bonus for 'Gambler' perk (ID 83) +;125 - maximum bonus, 20 - default bonus +GamblerBonus=20 + +;Changes the Steal skill bonus for 'Harmless' perk (ID 91) +;125 - maximum bonus, 20 - default bonus +HarmlessBonus=20 + +;Changes the damage bonus and the Doctor skill bonus for 'Living Anatomy' perk (ID 97) +;125 - maximum bonus, 5 - default bonus, 10 - default skill bonus LivingAnatomyBonus=5 +LivingAnatomyDoctorBonus=10 + +;Changes the Speech and Barter skill bonus for 'Negotiator' perk (ID 99) +;125 - maximum bonus, 10 - default bonus +NegotiatorBonus=10 ;Changes the damage bonus for 'Pyromaniac' perk (ID 101) ;125 - maximum bonus, 5 - default bonus @@ -50,10 +92,23 @@ SalesmanBonus=20 ;valid range: 0..100, 50 - default percent StonewallPercent=50 +;Changes the Lockpick, Steal, and Traps skill bonus for 'Thief' perk (ID 105) +;125 - maximum bonus, 10 - default bonus +ThiefBonus=10 + ;Changes the strength bonus for 'Weapon Handling' perk (ID 106) ;0 - no bonus, 10 - maximum bonus, 3 - default bonus WeaponHandlingBonus=3 +;Changes the skill bonuses for 'Vault City Training' perk (ID 107) +;125 - maximum bonus, 5 - default bonus +VaultCityTrainingFirstAidBonus=5 +VaultCityTrainingDoctorBonus=5 + +;Changes the Speech skill bonus for 'Expert Excrement Expeditor' perk (ID 116) +;125 - maximum bonus, 5 - default bonus +ExpertExcrementExpeditorBonus=5 + ;############################################################################## [Perks] ;Set to 1 to enable the modifications for perks diff --git a/artifacts/config_files/sfall-mods.ini b/artifacts/config_files/sfall-mods.ini index e5c83dbc0..0c59a0b7d 100644 --- a/artifacts/config_files/sfall-mods.ini +++ b/artifacts/config_files/sfall-mods.ini @@ -9,10 +9,13 @@ Containers=0 ; Set to 1 to also highlight lootable corpses Corpses=0 +; Set to 1 to also highlight critters, with the same rules as in combat +Critters=0 + ; Set to 1 to only highlight objects in the player's line-of-sight -CheckLOS=0 +CheckLOS=1 -; Set the color of outlines, available colors: +; Set the color of outlines for items, corpses, and containers; available colors: ; 1 - glowing red ; 2 - red ; 4 - grey @@ -20,7 +23,7 @@ CheckLOS=0 ; 16 - bright yellow ; 32 - dark yellow ; 64 - purple -; You can set a custom color from the game palette by multiplying the color index value by 256 (available since sfall 4.2.7) +; You can set a custom color from the game palette by multiplying the color index value by 256 (in 4.2.7 or later) OutlineColor=16 ; Motion Scanner mode: @@ -31,11 +34,11 @@ MotionScanner=0 [CombatControl] -;Allows you to directly control other critters in combat +;Allows you to control other critters directly in combat ;Set to 0 to disable ;Set to 1 to control all critters in combat (allowed only in sfall debugging mode) ;Set to 2 to control all party members -;Set to 3 to let you order party members to attack specified targets after your first attack instead of controlling them directly +;Set to 3 to let you use the command cursor to specify targets for party members to attack in combat Mode=0 ;If you want to control only specific critters, uncomment the PIDList line and set a comma delimited list of PIDs ;PIDList=62,89,97,107,160,161 diff --git a/artifacts/ddraw.ini b/artifacts/ddraw.ini index c03c86d27..d8e87309f 100644 --- a/artifacts/ddraw.ini +++ b/artifacts/ddraw.ini @@ -1,5 +1,5 @@ ;sfall configuration settings -;v4.3.8 +;v4.4 [Main] ;Set to 1 to enable the built-in High Resolution Patch mode that is similar to the hi-res patch by Mash @@ -19,14 +19,19 @@ UseCommandLine=0 ;This section allows you to set multiple paths to folders containing mods or patches ;Paths to folders and Fallout .dat files are supported ;The PatchFileXX options are available from 0 to 99. Larger numbers take precedence over smaller numbers (same as patchXXX.dat) -;Starting from 4.2.6, the game will automatically search and load custom .dat files (or folders named as .dat files) from \mods\ -;The load order of files in the folder will be in reverse alphabetical order of the filenames -;Autoloaded files will have a higher priority than the files specified in the PatchFileXX options -;The complete data load order is: -;master_patches > critter_patches > mods > PatchFile99..PatchFile0 > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat +;Starting from 4.4/3.8.40, the game will load custom .dat files and folders from \mods\mods_order.txt +;The files and folders in mods_order.txt will have a higher priority than the PatchFileXX options +;The complete order of how the engine loads game data is: +;master_patches > critter_patches > mods_order.txt > PatchFileXX > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat ;PatchFile0=mods\RP_data ;PatchFile1= +;Some details about mods_order.txt: +;If mods_order.txt does not exist, sfall will create one in \mods\ +;To install a new mod, copy the file/folder to \mods\ and add its name to mods_order.txt +;Mods will be loaded line by line from top to bottom. You can change the load order in a text editor +;To disable a mod temporarily, you can comment it out by adding a ';' or '#' to the beginning of the line + ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Speed] ;Set to 1 to enable options related to the game speed adjustment (SpeedMulti#, SpeedKey#, SpeedModKey, and SpeedToggleKey) @@ -65,7 +70,7 @@ GraphicsWidth=0 GraphicsHeight=0 ;Window position data. Do not modify -;Set to -1 or 0 to reset if the window position is incorrect +;Set to -1 or 0 to reset the window position to the center or top-left corner WindowData=-1 ;Uncomment the option to use a hardware shader (requires DX9 graphics mode) @@ -85,7 +90,7 @@ TextureFilter=1 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\\ (without 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) @@ -150,6 +155,15 @@ OverrideMusicDir=1 ;This will slightly increase the startup time of the game on older computers AutoSearchSFX=1 +;Uncomment these lines to override the names of sound files used by the engine +;Filenames are limited to 8 characters (without extension) +;MainMenuMusic=07desert +;WorldMapMusic=23world +;WorldMapCarMusic=20car +;EndGameMovieMusic0=akiss +;EndGameMovieMusic1=10labone +;MapLoadingSound=wind2 + ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Input] ;Set to 1 to enable the mouse scroll wheel to scroll through the inventory, barter, and loot screens @@ -277,9 +291,9 @@ UseFileSystemOverride=0 ;If you want to load multiple patch files (up to 1000) at once, you can include a %d in the file name (sprintf syntax) ;PatchFile=patch%03d.dat -;Set to 1 to use the modified data load order for the engine to find game data -;Original: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat -;Modified: master_patches > critter_patches > extra_patches > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat +;Set to 1 to change the order of how the engine loads game data +;Old: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat +;New: master_patches > critter_patches > [ExtraPatches] > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat ;This option is always enabled in 4.3/3.8.30 or later. The information is left for reference only DataLoadOrderPatch=1 @@ -287,21 +301,20 @@ DataLoadOrderPatch=1 ;Set to 2 to also load subtitle files from text\\cuts_female\ for female PC FemaleDialogMsgs=0 -;Set to 1 to allow using the special '^' character in dialog msg files to specify the alternative text -;that will be displayed in the dialogue based on the player's gender +;Set to 1 to allow using the caret character '^' in dialog msg files to specify alternative text in dialogue based on the player's gender ;The text must be enclosed in angle brackets (example: ) DialogGenderWords=0 ;To change the default and starting player models, uncomment the next four lines. -;The default models can also be changed ingame via script +;The default models can also be changed in-game via script ;MaleStartModel=hmwarr ;MaleDefaultModel=hmjmps ;FemaleStartModel=hfprim ;FemaleDefaultModel=hfjmps -;To change the various ingame movies, modify the next 17 lines +;To change the various in-game movies, modify the next 17 lines ;You can also define additional movies by adding Movie18 - Movie32 lines -;Most of these can also be changed ingame via script +;Most of these can also be changed in-game via script Movie1=iplogo.mve Movie2=intro.mve Movie3=elder.mve @@ -515,7 +528,7 @@ ScienceOnCritters=0 ;Default is 166 (lower - faster; valid range: 0..1000) SpeedInventoryPCRotation=166 -;Modify the number of the extra interface boxes available to modders. (Default is 5, and the maximum is 95) +;Modify the number of the extra interface boxes available to modders (Default is 5, and the maximum is 95) BoxBarCount=5 ;Uncomment to set the text colour of the extra interface boxes @@ -528,10 +541,10 @@ BonusHtHDamageFix=1 ;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory DisplayBonusDamage=0 -;Modify the maximum number of animations allowed to run on a map. (Default is 32, and the maximum is 127) +;Modify the maximum number of animations allowed to run on a map (Default is 32, and the maximum is 127) AnimationsAtOnceLimit=64 -;Set to 1 to remove the limits that stop the player rolling critical successes/misses in the first few days of game time +;Set to 1 to remove the limits that stop the game from rolling critical successes/failures in the first few days of game time RemoveCriticalTimelimits=0 ;Change the colour of the font used on the main menu for the Fallout/sfall version string and copyright text @@ -617,10 +630,10 @@ ExplosionsEmitLight=0 ;MovieTimer_artimer3=270 ;MovieTimer_artimer4=360 -;Set to 1 to add proper check for ammo before attacking and proper calculation of the number of burst rounds -;By default, weapons can make attacks when at least one ammo is left, regardless of ammo cost calculations +;Set to 1 to add proper checks for ammo before attacking +;By default, a weapon can perform an attack with at least one ammo, regardless of ammo cost calculation ;Note that enabling this option will prevent super cattle prods and mega power fists from attacking with only one ammo left -CheckWeaponAmmoCost=0 +CheckWeaponAmmoCost=1 ;Controls the speed of combat panel animations (lower - faster; valid range: 0..65535) CombatPanelAnimDelay=1000 @@ -741,7 +754,7 @@ AIBestWeaponFix=1 ;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as a preference list when using drugs in their inventory ;Set to 2 to allow NPCs to use only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder) -;Note: chem_primary_desire w/o fixes prevents the specified item PID from being consumed if all three values in the list are the same PID +;Note: chem_primary_desire without fixes prevents the specified item from being consumed if all three values in the list are the same PID ;chem_primary_desire also works as a priority list of drug items the NPC will try to pick up in combat when they are on the ground AIDrugUsePerfFix=0 @@ -809,10 +822,9 @@ DisableSpecialAreas=0 ;Paths outside of scripts folder are supported GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int -;Uncomment the option to specify an additional folder path for ini files used by scripts -;The game will search for the ini files relative to this directory -;If the file is not found, the search will proceed relative to the root directory of the game -;The maximum length of the specified path is 61 characters +;Uncomment the option to specify an additional directory for ini files used by scripts +;The game will search for ini files first relative to this directory and then relative to the root directory if not found +;The path length is limited to 61 characters ;IniConfigFolder=mods\iniConfigs ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -838,7 +850,7 @@ AllowUnsafeScripting=0 SkipCompatModeCheck=0 ;Fallout 2 Debug Patch -;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and debug.log +;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both ;Does not require sfall debugging mode ;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg ;------- @@ -876,7 +888,7 @@ AlwaysFindScripts=0 Test_ForceFloats=0 ;These options control what output is saved in the debug log (sfall-log.txt) -;Prints messages duing sfall initialization +;Prints messages during sfall initialization Init=1 ;Prints messages relating to hook scripts Hook=0 @@ -886,3 +898,19 @@ Script=0 Criticals=1 ;Prints messages relating to engine fixes Fixes=1 + +;Duplicates logs to a dedicated console window alongside the game window +;This option uses bit flags to control the types of messages to be shown +;All types other than sfall log (bit 1) require DebugMode to be enabled +;1 (bit 0) - debug output from the engine +;2 (bit 1) - sfall log +;4 (bit 2) - messages from debug_msg script function +;8 (bit 3) - messages from display_msg script function +ConsoleWindow=0b0000 + +;Console window position and size data. Do not modify +;Clear the data to reset the window position and size +ConsoleWindowData= + +;Set the code page for the console window (Default is your system code page) +ConsoleCodePage=0 diff --git a/artifacts/example_mods/BurstControl/gl_burstcontrol.ssl b/artifacts/example_mods/BurstControl/gl_burstcontrol.ssl new file mode 100644 index 000000000..e84217a3f --- /dev/null +++ b/artifacts/example_mods/BurstControl/gl_burstcontrol.ssl @@ -0,0 +1,30 @@ +/* + A basic example of how to use set_spray_settings function for script-based burst control +*/ + +#include "..\headers\define.h" +#include "..\headers\sfall\define_extra.h" +#include "..\headers\sfall\sfall.h" + +procedure start; +procedure ammocost_handler; + +procedure start begin + if game_loaded then begin + register_hook_proc(HOOK_AMMOCOST, ammocost_handler); + end +end + +procedure ammocost_handler begin + variable + weapon := get_sfall_arg, + bullets := get_sfall_arg, + cost := get_sfall_arg, + event := get_sfall_arg; + + if (event == 2 and weapon > 0) then begin // when calculating number of burst rounds + if (obj_pid(weapon) == PID_ASSAULT_RIFLE) then begin + set_spray_settings(1, 1, 1, 1); // burst attack will not hit other targets + end + end +end diff --git a/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl b/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl index 44d329342..ea98cf7f6 100644 --- a/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl +++ b/artifacts/example_mods/ComputeDamage/gl_compute_damage.ssl @@ -1,28 +1,69 @@ /* - Example algorithm of how the game engine calculates combat damage + This script reimplements the combat damage calculation of the original game. + Use this to implement your own damage formula! */ -#include "..\headers\define.h" -#include "..\headers\command.h" -#include "..\headers\sfall\sfall.h" -#include "..\headers\sfall\define_extra.h" +#include "../sfall/define_lite.h" +#include "../sfall/define_extra.h" +#include "../sfall/sfall.h" + +#define DAMAGE_FORMULA_VANILLA (0) +#define DAMAGE_FORMULA_GLOVZ (1) +#define DAMAGE_FORMULA_GLOVZ2 (2) +#define DAMAGE_FORMULA_YAAM (5) procedure start; -procedure compute_damage_F2; -procedure item_w_damage_hook; +procedure combatdamage_handler; +procedure itemdamage_handler; +procedure calc_damage_YAAM(variable weapon, variable rounds, variable armorDT, variable armorDR, variable bonusRangedDamage, variable multiplyDamage, variable difficulty); procedure item_w_subtype(variable weapon, variable hit_mode); procedure get_ammo_value(variable weapon, variable param); -variable item_w_damage; +variable + damage_formula, + item_damage_min, + item_damage_max, + item_damage_weapon, + item_damage_attacker; procedure start begin if game_loaded then begin - register_hook_proc(HOOK_COMBATDAMAGE, compute_damage_F2); - register_hook_proc(HOOK_ITEMDAMAGE, item_w_damage_hook); + damage_formula := get_ini_setting("ddraw.ini|Misc|DamageFormula"); + register_hook_proc(HOOK_COMBATDAMAGE, combatdamage_handler); + register_hook_proc(HOOK_ITEMDAMAGE, itemdamage_handler); end end -procedure compute_damage_F2 begin +/* + HOOK_COMBATDAMAGE + + Runs when: + 1) Game calculates how much damage each target will get. This includes primary target as well as all extras (explosions and bursts). This happens BEFORE the actual attack animation. + 2) AI decides whether it is safe to use area attack (burst, grenades), if he might hit friendlies. + + Does not run for misses, or non-combat damage like dynamite explosions. + + Critter arg0 - The target + Critter arg1 - The attacker + int arg2 - The amount of damage to the target + int arg3 - The amount of damage to the attacker + int arg4 - The special effect flags for the target (use bwand DAM_* to check specific flags) + int arg5 - The special effect flags for the attacker (use bwand DAM_* to check specific flags) + Item arg6 - The weapon used in the attack + int arg7 - The bodypart that was struck + int arg8 - Damage Multiplier (this is divided by 2, so a value of 3 does 1.5x damage, and 8 does 4x damage. Usually it's 2; for critical hits, the value is taken from the critical table; with Silent Death perk and the corresponding attack conditions, the value will be doubled) + int arg9 - Number of bullets actually hit the target (1 for melee attacks) + int arg10 - The amount of knockback to the target + int arg11 - Attack Type (see ATKTYPE_* constants) + mixed arg12 - computed attack data (see C_ATTACK_* for offsets and use get/set_object_data functions to get/set the data) + + int ret0 - The damage to the target + int ret1 - The damage to the attacker + int ret2 - The special effect flags for the target + int ret3 - The special effect flags for the attacker + int ret4 - The amount of knockback to the target +*/ +procedure combatdamage_handler begin variable dmg_type, weapon_perk, dmg_thresh, dmg_resist, weapon_subtype, bonus_ranged, difficulty, i, dmg_mult, dmg_div, damage; variable target, flags, knockback, amount; @@ -40,6 +81,11 @@ procedure compute_damage_F2 begin amountKnockback := get_sfall_arg, hit_mode := get_sfall_arg; + if (ctdSource != item_damage_attacker or weapon != item_damage_weapon) then begin + debug_msg("! ERROR ! compute_damage: Expected attacker or weapon differs!"); + return; + end + if (flagsSource bwand DAM_HIT) then begin target := ctdTarget; flags := flagsTarget; @@ -50,7 +96,6 @@ procedure compute_damage_F2 begin knockback := 0; end - //amount := 0; if target and (obj_type(target) == OBJ_TYPE_CRITTER) then begin if weapon then begin dmg_type := weapon_dmg_type(weapon); @@ -77,7 +122,7 @@ procedure compute_damage_F2 begin end weapon_subtype := item_w_subtype(weapon, hit_mode); // item_w_subtype_ - if (ctdSource != dude_obj) or (weapon_subtype != WEAPON_TYPE_GUNS) then + if (ctdSource != dude_obj) or (weapon_subtype != WEAPON_TYPE_RANGED) then bonus_ranged := 0; else bonus_ranged := has_trait(TRAIT_PERK, ctdSource, PERK_bonus_ranged_damage) * 2; @@ -90,27 +135,31 @@ procedure compute_damage_F2 begin difficulty := 125; end - // F2 default start - // Damage = (1 - (DR_armor + DR_ammo_adjust) * (((raw_damage * (dmg_mult * damageMultiplier)) / dmg_div) - dmg_thresh) - dmg_resist += get_ammo_value(weapon, PROTO_AM_DR_MOD); // item_w_dr_adjust_ (DR Adjust %) - if (dmg_resist < 100) then begin - if (dmg_resist < 0) then dmg_resist := 0; - - dmg_mult := damageMultiplier * get_ammo_value(weapon, PROTO_AM_DMG_MULT); // item_w_dam_mult_ (Dmg mod A) - dmg_div := get_ammo_value(weapon, PROTO_AM_DMG_DIV); // item_w_dam_div_ (Dmg mod B) - - for (i := 1; i <= rounds; i++) begin - damage := (item_w_damage + bonus_ranged) * dmg_mult; // item_w_damage_ (raw_damage) - if dmg_div then damage /= dmg_div; - - damage := (((damage / 2) * difficulty) / 100) - dmg_thresh; - if (damage > 0) then begin - damage := damage - ((damage * dmg_resist) / 100); // reduce damage by the percentage of DR_armor + DR_Ammo - if (damage > 0) then amount += damage; + if (damage_formula == DAMAGE_FORMULA_YAAM) then begin + amount := calc_damage_YAAM(weapon, rounds, dmg_thresh, dmg_resist, bonus_ranged, damageMultiplier, difficulty); + end else begin + // F2 default start + // Damage = (1 - (DR_armor + DR_ammo_adjust) * (((raw_damage * (dmg_mult * damageMultiplier)) / dmg_div) - dmg_thresh) + dmg_resist += get_ammo_value(weapon, PROTO_AM_DR_MOD); // item_w_dr_adjust_ (DR Adjust %) + if (dmg_resist < 100) then begin + if (dmg_resist < 0) then dmg_resist := 0; + + dmg_mult := damageMultiplier * get_ammo_value(weapon, PROTO_AM_DMG_MULT); // item_w_dam_mult_ (Dmg mod A) + dmg_div := get_ammo_value(weapon, PROTO_AM_DMG_DIV); // item_w_dam_div_ (Dmg mod B) + + for (i := 1; i <= rounds; i++) begin + damage := (random(item_damage_min, item_damage_max) + bonus_ranged) * dmg_mult; // item_w_damage_ (raw_damage) + if dmg_div then damage /= dmg_div; + + damage := (((damage / 2) * difficulty) / 100) - dmg_thresh; + if (damage > 0) then begin + damage := damage - ((damage * dmg_resist) / 100); // reduce damage by the percentage of DR_armor + DR_Ammo + if (damage > 0) then amount += damage; + end end end + // F2 default end end - // F2 default end if (ctdSource == dude_obj) then begin if has_trait(TRAIT_PERK, ctdSource, PERK_living_anatomy_perk) and (critter_kill_type(ctdTarget) != KILL_TYPE_robot_kills) @@ -140,11 +189,11 @@ procedure compute_damage_F2 begin end if (flagsSource bwand DAM_HIT) then begin - display_msg("amountTarget = " + amountTarget+ ", amount = " + amount); + display_msg("COMBATDAMAGE amountTarget = " + amountTarget+ ", amount = " + amount); amountTarget := amount; flagsTarget := flags; end else begin - display_msg("amountSource = " + amountSource+ ", amount = " + amount); + display_msg("COMBATDAMAGE amountSource = " + amountSource+ ", amount = " + amount); amountSource := amount; flagsSource := flags; end @@ -154,40 +203,118 @@ procedure compute_damage_F2 begin set_sfall_return(flagsTarget); set_sfall_return(flagsSource); set_sfall_return(amountKnockback); + + set_sfall_arg(2, amountTarget); + set_sfall_arg(3, amountSource); + set_sfall_arg(4, flagsTarget); + set_sfall_arg(5, flagsSource); + set_sfall_arg(10, amountKnockback); end -procedure item_w_damage_hook begin - variable - item_w_damage_min := get_sfall_arg, - item_w_damage_max := get_sfall_arg; - //weapon := get_sfall_arg, - //source := get_sfall_arg, +procedure calc_damage_YAAM(variable weapon, variable rounds, variable armorDT, variable armorDR, variable bonusRangedDamage, variable multiplyDamage, variable difficulty) begin + variable accumulatedDamage, ammoDiv, ammoMult, ammoDT, calcDT, _calcDT, calcDR, i, rawDamage, resistedDamage; + if (rounds <= 0) then begin + return 0; + end + + ammoDiv := get_ammo_value(weapon, PROTO_AM_DMG_DIV); + ammoMult := get_ammo_value(weapon, PROTO_AM_DMG_MULT); + + // Damage Multipler = Critical Multipler * Ammo Dividend + multiplyDamage *= ammoMult; + + // Retrieve ammo DT (well, it's really Retrieve ammo DR, but since we're treating ammo DR as ammo DT...) + ammoDT := get_ammo_value(weapon, PROTO_AM_DR_MOD); + + calcDT := armorDT - ammoDT; + _calcDT := calcDT; + + if (calcDT >= 0) then begin + _calcDT := 0; + end else begin + // note that this should be a negative value + _calcDT *= 10; + calcDT := 0; + end + + // DR = armor DR + DT (note that DT should be less than or equal to zero) + calcDR = armorDR + _calcDT; + if (calcDR < 0) then begin + calcDR = 0; + end else if (calcDR >= 100) then begin + return 0; + end + + display_msg("YAAM: AmmoDT=" + ammoDT + ", DT=" + calcDT + "/" + armorDT + ", DR=" + calcDR + "/" + armorDR); + // Start of damage calculation loop + for (i := 0; i < rounds; i++) begin + rawDamage = random(item_damage_min, item_damage_max); + rawDamage += bonusRangedDamage; + + rawDamage -= calcDT; + if (rawDamage <= 0) then + continue; + + rawDamage *= multiplyDamage; + if (ammoDiv != 0) then begin + rawDamage /= ammoDiv; + end + rawDamage /= 2; // related to critical hit damage multiplier bonus + rawDamage *= difficulty; // combat difficulty setting (75 if wimpy, 100 if normal or if attacker is player, 125 if rough) + rawDamage /= 100; + + resistedDamage = calcDR * rawDamage; + resistedDamage /= 100; + rawDamage -= resistedDamage; + + if (rawDamage > 0) then begin + display_msg("YAAM: Bullet " + i + " dmg=" + rawDamage); + accumulatedDamage += rawDamage; + end + end + return accumulatedDamage; +end + +/* + HOOK_ITEMDAMAGE + + Runs when retrieving the damage rating of the player's used weapon. (Which may be their fists.) + + int arg0 - The default min damage + int arg1 - The default max damage + Item arg2 - The weapon used (0 if unarmed) + Critter arg3 - The critter doing the attacking + int arg4 - The type of attack + int arg5 - non-zero if this is an attack using a melee weapon + + int ret0 - Either the damage to be used, if ret1 isn't given, or the new minimum damage if it is + int ret1 - The new maximum damage +*/ +procedure itemdamage_handler begin + item_damage_min := get_sfall_arg; + item_damage_max := get_sfall_arg; + item_damage_weapon := get_sfall_arg; + item_damage_attacker := get_sfall_arg; //hit_mode := get_sfall_arg, //isMelee := get_sfall_arg; - item_w_damage := (item_w_damage_min + (item_w_damage_max - item_w_damage_min) / 2); - display_msg("item_w_damage_ = " + item_w_damage); - - set_sfall_return(item_w_damage); + display_msg("itemdamage: " + item_damage_min + "-" + item_damage_max); end procedure item_w_subtype(variable weapon, variable hit_mode) begin variable attack_mode, type := WEAPON_TYPE_UNARMED; if weapon and (hit_mode <= ATKTYPE_RWEP2) then begin - attack_mode := (get_proto_data(obj_pid(weapon), PROTO_IT_FLAGS)); - - if (hit_mode == ATKTYPE_LWEP2) or (hit_mode == ATKTYPE_RWEP2) then - attack_mode := (attack_mode bwand 0xF0) / 16; // shift 4 bits to the right - else - attack_mode := (attack_mode bwand 0x0F); + attack_mode := weapon_attack_mode(obj_pid(weapon), hit_mode); - if (attack_mode > ATKMODE_PRI_THROW) then - type := WEAPON_TYPE_GUNS; - else if (attack_mode == ATKMODE_PRI_THROW) then + if (attack_mode >= ATTACK_MODE_SINGLE) then + type := WEAPON_TYPE_RANGED; + else if (attack_mode == ATTACK_MODE_THROW) then type := WEAPON_TYPE_THROWN; - else if (attack_mode > ATKMODE_PRI_KICK) then + else if (attack_mode >= ATTACK_MODE_SWING) then type := WEAPON_TYPE_MELEE; + else if (attack_mode == ATTACK_MODE_NONE) then + type := WEAPON_TYPE_NONE; end return type; diff --git a/artifacts/example_mods/RestEncounter/gl_rest_encounter.int b/artifacts/example_mods/RestEncounter/gl_rest_encounter.int new file mode 100644 index 000000000..1ae496534 Binary files /dev/null and b/artifacts/example_mods/RestEncounter/gl_rest_encounter.int differ diff --git a/artifacts/example_mods/RestEncounter/gl_rest_encounter.ssl b/artifacts/example_mods/RestEncounter/gl_rest_encounter.ssl new file mode 100644 index 000000000..e2ea5c715 --- /dev/null +++ b/artifacts/example_mods/RestEncounter/gl_rest_encounter.ssl @@ -0,0 +1,65 @@ +/* + +Resting Encounters mod for Fallout 2 by Lexx +-------------------------------------------- + +- Check where we are on the world map and overwrite the default desert "resting" map +- Compatible with vanilla Fallout 2 and UP/RP + +Requires sfall 4.3.6/3.8.38 or higher + +*/ + +#include "..\headers\define.h" +#include "..\headers\sfall\sfall.h" +#include "..\headers\sfall\lib.arrays.h" + +procedure start; +procedure encounter_handler; + +#define is_terrain_desert (get_current_terrain_name == mstr_worldmap(1000)) +#define is_terrain_mountains (get_current_terrain_name == mstr_worldmap(1001)) +#define is_terrain_city (get_current_terrain_name == mstr_worldmap(1002)) +#define is_terrain_coast (get_current_terrain_name == mstr_worldmap(1003)) + +procedure start begin + if (game_loaded) then begin + register_hook_proc(HOOK_ENCOUNTER, encounter_handler); + end +end + +procedure encounter_handler begin + variable + event := get_sfall_arg, + mapID := get_sfall_arg, + mapsList; + + // If the player enters a map on the world map, we set it depending on the current terrain type + // event 0 is random encounter, 1 is player initiated "encounter" + if (event == 1 AndAlso mapID == MAP_RND_DESERT_1) then begin + if (is_terrain_desert) then begin + mapsList := [MAP_RND_DESERT_1]; + if (global_var(GVAR_CAR_PLACED_TILE) < 0) then set_car_current_town(AREA_RND_DESERT); + end + else if (is_terrain_mountains) then begin + mapsList := [MAP_RND_MOUNTAIN1, MAP_RND_MOUNTAIN2]; + if (global_var(GVAR_CAR_PLACED_TILE) < 0) then set_car_current_town(AREA_RND_MOUNTAIN); + end + else if (is_terrain_city) then begin + mapsList := [MAP_RND_CITY1, MAP_RND_CITY_2, MAP_RND_CITY_3, MAP_RND_CITY_4, MAP_RND_CITY_5, MAP_RND_CITY_6, MAP_RND_CITY_7, MAP_RND_CITY_8]; + if (global_var(GVAR_CAR_PLACED_TILE) < 0) then set_car_current_town(AREA_RND_CITY); + end + else if (is_terrain_coast) then begin + mapsList := [MAP_RND_COAST1, MAP_RND_COAST2]; + if (global_var(GVAR_CAR_PLACED_TILE) < 0) then set_car_current_town(AREA_RND_COAST); + end + else begin + mapsList := [MAP_RND_DESERT_1]; + if (global_var(GVAR_CAR_PLACED_TILE) < 0) then set_car_current_town(AREA_RND_DESERT); + debug_msg("!!! COULDN'T DETECT TERRAIN TYPE !!!"); + end + + mapID := array_random_value(mapsList); + set_sfall_return(mapID); + end +end diff --git a/artifacts/example_mods/RestEncounter/readme.txt b/artifacts/example_mods/RestEncounter/readme.txt new file mode 100644 index 000000000..2c13df7a6 --- /dev/null +++ b/artifacts/example_mods/RestEncounter/readme.txt @@ -0,0 +1,12 @@ +Resting Encounters mod for Fallout 2 by Lexx +-------------------------------------------- + +- Check where we are on the world map and overwrite the default desert "resting" map +- Compatible with vanilla Fallout 2 and UP/RP + + +Requires sfall 4.3.6/3.8.38 or higher. + +To use, copy gl_rest_encounter.int to your scripts folder. + +This mod is an example of how you can use HOOK_ENCOUNTER hooks. diff --git a/artifacts/mods/gl_highlighting.int b/artifacts/mods/gl_highlighting.int index ecfbf585b..406a49f48 100644 Binary files a/artifacts/mods/gl_highlighting.int and b/artifacts/mods/gl_highlighting.int differ diff --git a/artifacts/mods/gl_highlighting.ssl b/artifacts/mods/gl_highlighting.ssl index eff9813ee..d71c824c5 100644 --- a/artifacts/mods/gl_highlighting.ssl +++ b/artifacts/mods/gl_highlighting.ssl @@ -1,11 +1,13 @@ /** - Item Highlight mod. + Item Highlighting mod Previously was part of sfall itself, now a separate mod. Features: - - highlighting items, containers (optional) and lootable corpses (optional) on the ground + - highlights items, containers (optional) and lootable corpses (optional) on the ground + - highlights critters using the same rules as combat mode highlighting (optional) - configurable hotkey is used to trigger highlight + - hotkey can be pressed and held to continuously update highlighted objects based on current position - only objects in direct line-of-sight of player are highlighted (optional) - motion scanner is required to enable highlight (optional) - motion scanner charges are decreased on each use (optional) @@ -13,7 +15,7 @@ NOTE: this script requires compiler from sfall modderspack with -s option (short circuit evaluation) - version 1.1 + version 1.2 **/ @@ -22,6 +24,7 @@ #define CRITTER_IS_DEAD (1) #define PID_MOTION_SENSOR (59) +#define REPEAT_FRAMES (10) #define NO_HIGHLIGHT(obj) (get_flags(obj) bwand FLAG_NOHIGHLIGHT) #define NO_STEAL(obj) (get_proto_data(obj_pid(obj), PROTO_CR_FLAGS) bwand CFLG_NOSTEAL) @@ -30,36 +33,70 @@ variable highlightKey; variable isHighlight; variable alsoContainer; variable alsoCorpse; +variable alsoCritter; variable checkLOS; variable outlineColor; variable motionScanner; variable highlightFailMsg1; variable highlightFailMsg2; -procedure ToggleHighlightObject(variable obj, variable enable) begin - if obj and (not enable or not checkLOS or not obj_blocking_line(dude_obj, tile_num(obj), BLOCKING_TYPE_SHOOT)) then begin - if (alsoContainer and obj_item_subtype(obj) == item_type_container) or (not NO_HIGHLIGHT(obj)) then begin - if (enable) then set_outline(obj, outlineColor); - else set_outline(obj, 0); +procedure DudeCanSee(variable obj) begin + variable block := obj_blocking_line(dude_obj, tile_num(obj), BLOCKING_TYPE_SHOOT); + return not block or block == obj; +end + +procedure DudeCanHear(variable obj) begin + variable hearDist := dude_perception * 5; + if (get_flags(obj) bwand FLAG_TRANSGLASS) then + hearDist /= 2; + + return tile_distance_objs(dude_obj, obj) <= hearDist; +end + +procedure GetOutlineColor(variable obj, variable isCritter) begin + if isCritter then begin + if get_team(obj) == get_team(dude_obj) then return OUTLINE_GREEN_GLOW; + if not DudeCanSee(obj) then begin + if (DudeCanHear(obj)) then return OUTLINE_DARK_YELLOW; + return 0; end + return OUTLINE_RED_GLOW; end + if checkLOS and not DudeCanSee(obj) then + return 0; + + return outlineColor; +end + +procedure ToggleHighlightObject(variable obj, variable enable, variable isCritter) begin + if (not alsoContainer or obj_item_subtype(obj) != item_type_container) and NO_HIGHLIGHT(obj) then return; + + set_outline(obj, GetOutlineColor(obj, isCritter) if enable else 0); end procedure ToggleHighlight(variable enable) begin - variable obj; + variable obj, isCorpse; foreach obj in list_as_array(LIST_GROUNDITEMS) begin - if obj != outlined_object then begin - call ToggleHighlightObject(obj, enable); + if obj and obj != outlined_object then begin + call ToggleHighlightObject(obj, enable, false); end end - if (alsoCorpse) then begin + if (alsoCorpse or alsoCritter) then begin foreach obj in list_as_array(LIST_CRITTERS) begin - if critter_state(obj) == CRITTER_IS_DEAD and not NO_STEAL(obj) then begin - call ToggleHighlightObject(obj, enable); + if obj and obj != dude_obj then begin + isCorpse := critter_state(obj) == CRITTER_IS_DEAD; + if (alsoCritter and not isCorpse) or (isCorpse and not NO_STEAL(obj)) then begin + call ToggleHighlightObject(obj, enable, not isCorpse); + end end end end tile_refresh_display; + if (checkLOS or alsoCritter) and enable != isHighlight then begin + set_global_script_repeat(REPEAT_FRAMES if enable else 0); + end + + isHighlight := enable; end procedure KeyPressHandler begin @@ -69,7 +106,7 @@ procedure KeyPressHandler begin if scanCode == highlightKey and not(get_game_mode bwand (INTFACELOOT bwor BARTER)) then begin if pressed then begin - isHighlight := true; + set_global_script_repeat(REPEAT_FRAMES); if motionScanner then begin scanner := obj_carrying_pid_obj(dude_obj, PID_MOTION_SENSOR); if scanner then begin @@ -92,7 +129,6 @@ procedure KeyPressHandler begin call ToggleHighlight(true); end end else begin - isHighlight := false; call ToggleHighlight(false); end end @@ -100,14 +136,12 @@ end procedure CombatTurnHandler begin if isHighlight then begin - isHighlight := false; call ToggleHighlight(false); end end procedure GameModeChangeHandler begin if isHighlight and (get_game_mode bwand (INTFACELOOT bwor BARTER)) then begin - isHighlight := false; call ToggleHighlight(false); end end @@ -118,12 +152,15 @@ procedure InventoryMoveHandler begin end procedure start begin - if game_loaded and (sfall_ver_major >= 4) then begin + if game_loaded then begin + if (sfall_ver_major < 4) then return; + call InitConfigs; highlightKey := GetConfig(configSection, "Key", 0); alsoContainer := GetConfig(configSection, "Containers", 0); alsoCorpse := GetConfig(configSection, "Corpses", 0); + alsoCritter := GetConfig(configSection, "Critters", 0); checkLOS := GetConfig(configSection, "CheckLOS", 0); outlineColor := GetConfig(configSection, "OutlineColor", 16); if (outlineColor < 1) then outlineColor := 64; @@ -136,5 +173,10 @@ procedure start begin register_hook_proc(HOOK_COMBATTURN, CombatTurnHandler); register_hook_proc(HOOK_GAMEMODECHANGE, GameModeChangeHandler); register_hook_proc(HOOK_INVENTORYMOVE, InventoryMoveHandler); + set_global_script_type(1); + end else begin + if isHighlight and not(get_game_mode bwand (INTFACELOOT bwor BARTER)) then begin + call ToggleHighlight(true); + end end end diff --git a/artifacts/mods/gl_npcarmor.int b/artifacts/mods/gl_npcarmor.int index 9f8c4ba23..0912fbc04 100644 Binary files a/artifacts/mods/gl_npcarmor.int and b/artifacts/mods/gl_npcarmor.int differ diff --git a/artifacts/mods/gl_npcarmor.ssl b/artifacts/mods/gl_npcarmor.ssl index 7719a7996..9b93fb274 100644 --- a/artifacts/mods/gl_npcarmor.ssl +++ b/artifacts/mods/gl_npcarmor.ssl @@ -164,7 +164,9 @@ end procedure start begin variable sect, sects, armorTypes, armorType, npc, pid, pids, i; - if game_loaded and (sfall_ver_major >= 4) then begin + if game_loaded then begin + if (sfall_ver_major < 4) then return; + armorTypes := get_ini_section(modIni, "ArmorTypes"); armorPidMap := create_array_map; foreach (armorType: pids in armorTypes) begin diff --git a/artifacts/mods/gl_partycontrol.int b/artifacts/mods/gl_partycontrol.int index 7ca2d0919..5843a2281 100644 Binary files a/artifacts/mods/gl_partycontrol.int and b/artifacts/mods/gl_partycontrol.int differ diff --git a/artifacts/mods/gl_partycontrol.ssl b/artifacts/mods/gl_partycontrol.ssl index 8420d6923..9e251afe6 100644 --- a/artifacts/mods/gl_partycontrol.ssl +++ b/artifacts/mods/gl_partycontrol.ssl @@ -2,7 +2,12 @@ NPC Combat Control - Allows to take control of your party member or other NPCs during combat + Previously was part of sfall itself, now a separate mod. + Features: + - allows you to take control of your party members or other NPCs during combat + - configurable list of perk IDs for perks being inherited from the player (optional, not all perks can work) + - allows the player to gain a positive reputation when killing NPCs while controlling other critters (optional) + - a notification box to display the name of the controlled critter above the interface bar (optional) NOTE: this script requires compiler from sfall modderspack with -s option (short circuit evaluation) @@ -15,6 +20,12 @@ #include "..\headers\critrpid.h" #include "main.h" +#ifndef DEBUG +#define DEBUGMSG(x) +#else +#define DEBUGMSG(x) debug_msg(x); +#endif + procedure start; procedure AllowControl(variable pid); procedure SetLight(variable critter, variable int, variable dist); @@ -48,25 +59,29 @@ procedure CombatTurn_Handler begin critter := get_sfall_arg, pid, perkID, level; - //display_msg("Combat Turn: " + status + ", by " + obj_name(critter) + "/" + critter + ", arg3: " + get_sfall_arg); + DEBUGMSG("Combat Turn: " + status + ", by " + obj_name(critter) + "/" + critter + ", arg3: " + get_sfall_arg) pid := obj_pid(critter); if (pid == PID_PLAYER and inControl == false) then return; if (pid != PID_PLAYER and controlMode != 1 and AllowControl(pid) == false) then begin + DEBUGMSG("Skip control.") + if (npcControl) then begin npcControl := 0; + set_dude_obj(real_dude_obj); call SetLight(dude_obj, dudeLightInt, dudeLightDist); // restore dude light - //move_to(dude_obj, dude_tile, dude_elevation); - intface_redraw; + move_to(dude_obj, dude_tile, dude_elevation); + + DEBUGMSG("Set dude after NPC control.") end - //display_msg("Skip control."); return; end if (status == 1) then begin set_dude_obj(critter); - //display_msg("Take control of: " + obj_name(critter)); + + DEBUGMSG("Take control of: " + obj_name(critter)) if (critter != real_dude_obj) then begin if (npcControl == 0) then begin @@ -118,13 +133,11 @@ procedure CombatTurn_Handler begin // remove perks before switching control (only work with 4.1.8+) if (dude_obj != real_dude_obj) then begin + DEBUGMSG("Remove perks after NPC control.") foreach (perkID in perksList) begin level := has_trait(TRAIT_PERK, real_dude_obj, perkID); if (level) then critter_rm_trait(critter, TRAIT_PERK, perkID, level); end - - if (status == 0) then set_dude_obj(0); // w/o redraw interface bar (otherwise it flickers when switching from the player to NPCs) - //display_msg("Return control to real dude!"); end if (status < 0) then begin @@ -175,11 +188,8 @@ procedure SetGlobalVar_Handler begin end procedure start begin - if (game_loaded and sfall_ver_major >= 4) then begin - //variable configSection := "CombatControl"; - - set_perk_ranks(PERK_gecko_skinning_perk, 1); - set_perk_level(PERK_gecko_skinning_perk, 999); // prevent it from appearing in the perk selection window + if game_loaded then begin + if (sfall_ver_major < 4) then return; controlMode := GetConfig("CombatControl", "Mode", 0); if (controlMode >= 3) then begin @@ -206,6 +216,9 @@ procedure start begin displayNameColor := GetConfig("CombatControl", "DisplayNameColor", 0); end + set_perk_ranks(PERK_gecko_skinning_perk, 1); + set_perk_level(PERK_gecko_skinning_perk, 999); // prevent it from appearing in the perk selection window + pidList := GetConfigListInt("CombatControl", "PIDList"); if (len_array(pidList) > 0) then fix_array(pidList); diff --git a/artifacts/mods/main.h b/artifacts/mods/main.h index 5f545e459..ad8a92dc2 100644 --- a/artifacts/mods/main.h +++ b/artifacts/mods/main.h @@ -12,7 +12,7 @@ #include "..\scripting\headers\lib.inven.h" */ -variable ini := "sfall-mods.ini"; +variable ini := "mods\\sfall-mods.ini"; variable translationIni; // Gets the integer value from the specified ini diff --git a/artifacts/mods/readme.txt b/artifacts/mods/readme.txt index ce09d6968..b47239493 100644 --- a/artifacts/mods/readme.txt +++ b/artifacts/mods/readme.txt @@ -1,2 +1,5 @@ To install each mod, copy *.int file into your \data\scripts\ folder. For gl_npcarmor.int, you also need to copy npcarmor.ini to your Fallout directory. + +gl_highlighting.int and gl_partycontrol.int are already included in sfall.dat. +You don't usually need to install them separately. diff --git a/artifacts/scripting/compiler/sslc_readme.md b/artifacts/scripting/compiler/sslc_readme.md index a8d320066..27fa1ced5 100644 --- a/artifacts/scripting/compiler/sslc_readme.md +++ b/artifacts/scripting/compiler/sslc_readme.md @@ -220,16 +220,6 @@ Syntax which requires sfall for compiled scripts to be interpreted is marked by end ``` -- Empty statements in blocks are allowed: This is just a convenience to save scripters a bit of memory. Some of the macros in the Fallout headers include their own semicolons while others do not. With the original compiler you had to remember which was which, and if you got it wrong the script would not compile. Now it's always safe to include your own semicolon, even if the macro already had its own. For example, this would not compile with the original sslc, but will with the sfall edition: - ``` - #define my_macro display_msg("foo"); - - procedure start begin - my_macro; - end - ``` - __NOTE:__ **Does not work currently.** - - Procedure stringify operator `@`: Designed to make callback-procedures a better option and allow for basic functional programming. Basically it replaces procedure names preceded by `@` by a string constant. - old: ``` @@ -360,6 +350,16 @@ There are several changes in this version of sslc which may result in problems f ### Changelog +**sfall 4.4:** +- fixed compiler crash when trying to define an exported procedure with variables +- fixed compiler giving "symbol or string expected" error when trying to call procedure using a string literal +- fixed optimizer not treating `call string_variable` as variable use +- fixed unused arguments in a procedure being removed incorrectly by the optimizer +- fixed unused string literals in an optimized-out procedure not being removed by the optimizer +- fixed `get_array` syntax not working for exported variables +- fixed optional arguments not working for imported procedures +- fixed compiler crash when an exported procedure has no body + **sfall 4.2.9:** - added support for additional universal opcodes `sfall_func7` and `sfall_func8` - fixed a compilation error when the script has a UTF-8 BOM @@ -368,7 +368,7 @@ There are several changes in this version of sslc which may result in problems f - added ability to declare local variables anywhere in the procedure body **sfall 4.2.3:** -- fixed compiler giving "assignment operator expected" error when a variable-like macro is not being defined properly +- fixed compiler giving "assignment operator expected" error when a variable-like macro is not defined properly - added new logical operators `AndAlso`, `OrElse` for short-circuit evaluation of logical expressions - added an alternative (C/Java-style) assignment operator `=` - added support for new `div` operator (unsigned integer division) diff --git a/artifacts/scripting/functions.yml b/artifacts/scripting/functions.yml index 8dc233bad..c90da5555 100644 --- a/artifacts/scripting/functions.yml +++ b/artifacts/scripting/functions.yml @@ -599,7 +599,7 @@ opcode: 0x8212 - name: Math - parent: Variables + parent: Utility items: - name: log detail: float log(float x) @@ -649,7 +649,9 @@ If both arguments are integers, the result will be an integer. - name: floor2 detail: int floor2(int/float value) - doc: Works just like vanilla floor function, but returns correct integers for negative values (vanilla floor function works exactly the same as `ceil` for negative values, much like `trunc` in C/C++). + doc: | + Works just like vanilla floor function, but returns correct integers for negative floats. + - __NOTE:__ vanilla `floor` function works exactly the same as `ceil` for negative floats (i.e. basically `trunc` in C/C++). macro: sfall.h - name: div detail: div(x, y) @@ -703,7 +705,7 @@ items: - name: metarule2_explosions detail: int metarule2_explosions(int arg1, int arg2) - doc: Was made as a dirty easy hack to allow dynamically change some explosion parameters (ranged attack). All changed parameters are reset to vanilla state automatically after each attack action. + doc: Was made as a quick-and-dirty hack to enable dynamic changes to some explosion parameters for ranged attacks. All changed parameters are automatically reset to vanilla state after each attack action. opcode: 0x8261 - name: set_attack_explosion_pattern @@ -767,7 +769,7 @@ items: - name: reg_anim_combat_check detail: void reg_anim_combat_check(int enable) - doc: Allows to enable all `reg_anim_*` functions in combat (including vanilla functions) if set to 0. It is automatically reset at the end of each frame, so you need to call it before `reg_anim_begin` - `reg_anim_end` block. + doc: Allows enabling all `reg_anim_*` functions in combat (including vanilla functions) if set to 0. It is automatically reset at the end of each frame, so you need to call it before `reg_anim_begin` - `reg_anim_end` block. opcode: 0x825c - name: reg_anim_destroy detail: void reg_anim_destroy(ObjectPtr) @@ -791,7 +793,7 @@ opcode: 0x825f - name: reg_anim_turn_towards detail: void reg_anim_turn_towards(ObjectPtr, int tile/target, delay) - doc: Makes object change its direction to face given tile num or target object. + doc: Makes object change its direction to face given tile number or target object. opcode: 0x8260 - name: reg_anim_callback detail: void reg_anim_callback(procedure proc) @@ -823,7 +825,11 @@ - name: Tiles and paths items: - name: get_tile_fid - detail: int get_tile_fid(int tile) + detail: int get_tile_fid(int tileData) + doc: | + - Returns FID information about the square under the given tile at elevation 0 + - Pass elevation as 4-bit number in bits 25-28 to access other elevations + - Pass result mode in bits 29-32: 0 - ground FID, 1 - roof FID, 2 - raw data. opcode: 0x823a - name: tile_under_cursor detail: int tile_under_cursor @@ -850,6 +856,14 @@ - returns the tile number at the x, y position relative to the top-left corner of the screen - if the position is outside of the range of tiles, it will return -1 macro: sfall.h + - name: get_tile_ground_fid + detail: int get_tile_ground_fid(int tileNum, int elevation) + doc: Returns FID of a ground tile at given tile number and elevation. + macro: sfall.h + - name: get_tile_roof_fid + detail: int get_tile_roof_fid(int tileNum, int elevation) + doc: Returns FID of a roof tile at given tile number and elevation. Note that FID of 1 is used when there is no actual roof. + macro: sfall.h - name: obj_blocking_line detail: ObjectPtr obj_blocking_line(ObjectPtr objFrom, int tileTo, int blockingType) @@ -1056,11 +1070,13 @@ `fixedParam`: the value that is passed to the `timed_event_p_proc` procedure for the `fixed_param` function macro: sfall.h -- name: Variables +- name: Utility items: - name: sprintf detail: string sprintf(string format, any value) - doc: Formats given value using standart syntax of C printf function (google "printf" for format details). However it is limited to formatting only 1 value. Can be used to get character by ASCII code ("%c"). + doc: | + Formats given value using standart syntax of C `printf` function (google "printf" for format details). However, it is limited to formatting only 1 value. + - Can be used to get character by ASCII code ("%c"). opcode: 0x8250 - name: typeof detail: int typeof(any value) @@ -1074,19 +1090,19 @@ opcode: 0x8238 - name: Strings - parent: Variables + parent: Utility items: - name: string_split detail: array string_split(string text, split) doc: "Takes a string and a seperator, searches the string for all instances of the seperator, and returns a temp array filled with the pieces of the string split at each instance. If you give an empty string as the seperator, the string is split into individual characters. You can use this to search for a substring in a string like this: `strlen(get_array(string_split(haystack, needle), 0))`" opcode: 0x8235 - name: substr - detail: string substr(string text, start, length) + detail: string substr(string text, int start, int length) doc: | Cuts a substring from a string starting at "start" up to "length" characters. The first character position is 0 (zero). - - If start is negative - it indicates starting position from the end of the string (for example `substr("test", -2, 2)` will return last 2 charactes: "st"). + - If start is negative - it indicates a position starting from the end of the string (for example `substr("test", -2, 2)` will return last 2 charactes: "st"). - If length is negative - it means so many characters will be omitted from the end of string (example: `substr("test", 0, -2)` will return string without last 2 characters: "te"). - - If length is zero - it will return a string from the starting position to the end of the string **New behavior** for sfall 4.2.2/3.8.22 + - If length is zero - it will return a string from the starting position to the end of the string. **New behavior** for sfall 4.2.2/3.8.22 opcode: 0x824e - name: strlen detail: int strlen(string text) @@ -1098,14 +1114,34 @@ opcode: 0x8251 - name: get_string_pointer detail: int get_string_pointer(string text) - doc: Returns a pointer to a string variable or to a text. + doc: | + (DEPRECATED) Returns a pointer to a string variable or to a text + - __NOTE:__ this function is intended for use only in `HOOK_DESCRIPTIONOBJ`. Starting from sfall 4.4/3.8.40, you can return normal strings directly in the hook without calling the function + macro: sfall.h + - name: string_find + detail: int string_find(string haystack, string needle) + doc: Returns the position of the first occurrence of a `needle` string in a `haystack` string, or -1 if not found. The first character position is 0 (zero). + macro: sfall.h + - name: string_find_from + detail: int string_find_from(string haystack, string needle, int pos) + doc: | + Works the same as `string_find`, except you can specify the position to start the search. + - If `pos` is negative - it indicates a position starting from the end of the string, similar to `substr()`. macro: sfall.h - name: string_format detail: string string_format(string format, any val1, any val2, ...) doc: | - Formats given value using standard syntax of C printf function (google "printf" for format details). However it is limited to formatting up to 4 values. - - formatting is only supported for %s and %d, and the format string is limited to 1024 characters - macro: sfall.h + Formats given values using standard syntax of C `printf` function (google "printf" for format details). However, it is limited to formatting up to 7 values. + - The format string is limited to 1024 characters + macro: sfall.h + - name: string_format_array + detail: string string_format_array(string format, int array) + doc: The same as string_format, but accepts an array of parameters. + macro: lib.strings.h + - name: string_replace + detail: string string_replace(string str, string search, string replace) + doc: Replaces all occurances of a given search string in a string with a given replacement string. + macro: lib.strings.h - name: string_to_case detail: string sfall_func2("string_to_case", string text, int toCase) doc: | @@ -1246,7 +1282,9 @@ opcode: 0x822e - name: get_array detail: mixed get_array(int arrayID, mixed key) - doc: 'Returns array value by key or index (shorthand: arrayID[key]).' + doc: | + Returns array value by key or index (shorthand: arrayID[key]). + - If array with given arrayID doesn't exist, returns 0. opcode: 0x822f - name: resize_array detail: void resize_array(int arrayID, int size) @@ -1524,11 +1562,11 @@ - name: set_critical_table detail: void set_critical_table(int crittertype, int bodypart, int level, int valuetype, int value) opcode: 0x81e1 - doc: Used for modifying the critical table. For details see [critical hit tables](http://falloutmods.wikia.com/wiki/Critical_hit_tables). Changes are not saved, and will reset to the defaults, (or to the contents of `CriticalOverrides.ini`, if it exists) at each game reload. Requires `OverrideCriticalTable` to be set to 1 in `ddraw.ini`. (Disabled by default, because it noticably increases loading times.) + doc: Used for modifying the critical table. For details see [critical hit tables](http://falloutmods.wikia.com/wiki/Critical_hit_tables). Changes are not saved, and will reset to the defaults (or to the contents of `CriticalOverrides.ini`, if it exists) at each game reload. Requires `OverrideCriticalTable` to be enabled in `ddraw.ini` (already enabled by default). - name: get_critical_table detail: int get_critical_table(int crittertype, int bodypart, int level, int valuetype) opcode: 0x81e2 - doc: Gets current critical table. For details see [critical hit tables](http://falloutmods.wikia.com/wiki/Critical_hit_tables). Requires `OverrideCriticalTable` to be set to 1 in `ddraw.ini`. (Disabled by default, because it noticably increases loading times.) + doc: Gets current critical table. For details see [critical hit tables](http://falloutmods.wikia.com/wiki/Critical_hit_tables). Requires `OverrideCriticalTable` to be enabled in `ddraw.ini` (already enabled by default). - name: reset_critical_table detail: void reset_critical_table(int crittertype, int bodypart, int level, int valuetype) opcode: 0x81e3 @@ -1556,6 +1594,29 @@ opcode: 0x8192 doc: Should only be used during the target critters turn while in combat. + - name: set_spray_settings + detail: void set_spray_settings(int centerMult, int centerDiv, int targetMult, int targetDiv) + doc: | + Allows changing the multipliers and divisors for the bullet distribution of burst attacks dynamically. All settings are automatically reset to default values (**ComputeSpray_\*** settings in ddraw.ini) after each attack action + - Should be called before the calculation of the bullet distribution (e.g. in `HOOK_TOHIT` or `HOOK_AMMOCOST`) + - `centerDiv/targetDiv`: the minimum value of divisor is 1 + - `centerMult/targetMult`: multiplier values are capped at divisor values + - __NOTE:__ refer to the description of **ComputeSpray_\*** settings in ddraw.ini for details of the bullet distribution of burst attacks + macro: sfall.h + + - name: + detail: int get_combat_free_move + doc: Returns available "bonus move" points of the current critter's turn. For NPCs, this is always 0 unless changed by `set_combat_free_move` + macro: sfall.h + + - name: set_combat_free_move + detail: void set_combat_free_move(int value) + doc: | + Allows changing "bonus move" points (yellow lights on the interface bar) that can only be used for movement, not attacking + - Can be called from `HOOK_COMBATTURN` at the start of the turn (will not work on `dude_obj`) + - Can be called from `HOOK_STDPROCEDURE` with `combat_proc` event (will work on both NPCs and `dude_obj`) + macro: sfall.h + - name: Car items: - name: set_car_current_town @@ -1801,23 +1862,43 @@ - name: get_ini_setting detail: int get_ini_setting(string setting) doc: | - - Reads an integer value from an ini file in the fallout directory. - - It only takes a single argument; seperate the file name, section and key with a "\|" character; e.g. `myvar:=get_ini_setting("myini.ini|mysec|var1")` If the file or key cannot be found, -1 is returned. + Reads an integer value from an ini file in the Fallout directory. + - It only takes a single argument; seperate the file name, section and key with a "\|" character; e.g. `myvar:=get_ini_setting("myini.ini|mysec|var1")` + - If the file or key cannot be found or the setting argument is in an invalid format, it returns -1. - The file name is limited to 63 chars, including the extension. - The section name is limited to 32 characters. - - It can also be used to get sfall's settings, by using `ddraw.ini` as the file name. + - It can also be used to get sfall settings by using **ddraw.ini** as the file name. opcode: 0x81ac - name: get_ini_string detail: string get_ini_string(string setting) - doc: Reads a string value from an ini file in the fallout directory. + doc: | + Reads a string value from an ini file in the Fallout directory. + - If the file or key cannot be found, it returns an empty string. + - If the setting argument is in an invalid format, it returns -1 (integer). opcode: 0x81eb - name: get_ini_section detail: array get_ini_section(string file, string sect) - doc: 'Returns an associative array of keys and values for a given INI file and section. NOTE: all keys and their values will be of String type.' + doc: | + Returns an associative array of keys and values for a given INI file and section. + - If the INI file is not found, it returns an empty array. + - __NOTE:__ all keys and their values will be of String type. macro: sfall.h - name: get_ini_sections detail: array get_ini_sections(string file) - doc: Returns an array of names of all sections in a given INI file. + doc: | + Returns an array of names of all sections in a given INI file. + - If the INI file is not found, it returns an empty array. + macro: sfall.h + - name: get_ini_config + detail: array get_ini_config(string file) + doc: | + Loads a given INI file and returns a permanent array (map) where keys are section names and values are permanent sub-arrays (maps) where keys and values are strings. + - Searches the file in the regular file system, like with all other ini-related functions. + - Subsequent calls for the same file will return the same array, unless it was disposed using `free_array`. + macro: sfall.h + - name: get_ini_config_db + detail: array get_ini_config_db(string file) + doc: Works exactly like `get_ini_config`, except it searches the file in database (DAT) files. If not found, then it will try the regular file system. macro: sfall.h - name: set_ini_setting detail: void set_ini_setting(string setting, int/string value) diff --git a/artifacts/scripting/headers/command_lite.h b/artifacts/scripting/headers/command_lite.h index 713d5f469..90d5485a3 100644 --- a/artifacts/scripting/headers/command_lite.h +++ b/artifacts/scripting/headers/command_lite.h @@ -31,6 +31,10 @@ #define dude_is_armed critter_is_armed(dude_obj) #define dude_wearing_armor critter_wearing_armor(dude_obj) +#define get_armor(cr) critter_inven_obj(cr,INVEN_TYPE_WORN) +#define dude_armor get_armor(dude_obj) +#define self_armor get_armor(self_obj) + #define dude_has_power_armor (((obj_is_carrying_obj_pid(dude_obj, PID_POWERED_ARMOR)) + \ (obj_is_carrying_obj_pid(dude_obj, PID_ADVANCED_POWER_ARMOR)) + \ (obj_is_carrying_obj_pid(dude_obj, PID_ADVANCED_POWER_ARMOR_MK2)) + \ @@ -82,12 +86,14 @@ (critter_state(dude_obj) bwand DAM_CRIP_ARM_LEFT) orElse \ (critter_state(dude_obj) bwand DAM_CRIP_ARM_RIGHT)) +#define get_cur_rot(cr) has_trait(TRAIT_OBJECT,cr,OBJECT_CUR_ROT) #define dude_cur_rot (has_trait(TRAIT_OBJECT,dude_obj,OBJECT_CUR_ROT)) #define dude_inv_rot ((dude_cur_rot + 3)%6) #define dude_tile (tile_num(dude_obj)) #define dude_elevation (elevation(dude_obj)) #define tile_behind_obj(who) (tile_num_in_direction(tile_num(who),((has_trait(TRAIT_OBJECT,who,OBJECT_CUR_ROT) + 3) % 6), 1)) +#define tile_behind_obj_rng(who, rng) (tile_num_in_direction(tile_num(who),((has_trait(TRAIT_OBJECT,who,OBJECT_CUR_ROT) + 3) % 6), rng)) #define dude_cur_hits (get_critter_stat(dude_obj,STAT_current_hp)) #define dude_max_hits (get_critter_stat(dude_obj,STAT_max_hp)) @@ -124,11 +130,33 @@ #define self_agility (get_critter_stat(self_obj,STAT_ag)) #define self_luck (get_critter_stat(self_obj,STAT_lu)) +// more stats +#define get_strength(cr) get_critter_stat(cr,STAT_st) +#define get_perception(cr) get_critter_stat(cr,STAT_pe) +#define get_endurance(cr) get_critter_stat(cr,STAT_en) +#define get_charisma(cr) get_critter_stat(cr,STAT_ch) +#define get_iq(cr) get_critter_stat(cr,STAT_iq) +#define get_agility(cr) get_critter_stat(cr,STAT_ag) +#define get_luck(cr) get_critter_stat(cr,STAT_lu) + +// gender +#define get_gender(cr) get_critter_stat(cr,STAT_gender) +#define is_male(cr) (get_gender(cr) == GENDER_MALE) +#define is_female(cr) (get_gender(cr) == GENDER_FEMALE) #define self_is_male (self_gender == GENDER_MALE) #define self_is_female (self_gender == GENDER_FEMALE) + #define self_is_armed critter_is_armed(self_obj) #define self_wearing_armor critter_wearing_armor(self_obj) +// perks and traits +#define get_perk(cr,perk) has_trait(TRAIT_PERK,cr,perk) +#define dude_perk(perk) get_perk(dude_obj,perk) +#define self_perk(perk) get_perk(self_obj,perk) +#define get_trait(cr,trait) has_trait(TRAIT_TRAIT,cr,trait) +#define dude_trait(trait) get_trait(dude_obj,trait) +#define self_trait(trait) get_trait(self_obj,trait) + #define self_carrying_laser_pistol ((obj_pid(critter_inven_obj(self_obj,INVEN_TYPE_LEFT_HAND)) == PID_LASER_PISTOL) orElse \ (obj_pid(critter_inven_obj(self_obj,INVEN_TYPE_RIGHT_HAND)) == PID_LASER_PISTOL)) @@ -143,12 +171,32 @@ #define self_elevation (elevation(self_obj)) #define self_pid (obj_pid(self_obj)) + +// team #define self_team has_trait(TRAIT_OBJECT,self_obj,OBJECT_TEAM_NUM) +#define get_team(cr) has_trait(TRAIT_OBJECT,cr,OBJECT_TEAM_NUM) +#define set_team(cr,team) critter_add_trait(cr,TRAIT_OBJECT,OBJECT_TEAM_NUM,team) +#define set_self_team(team) set_team(self_obj,team) + +// ai #define self_ai has_trait(TRAIT_OBJECT,self_obj,OBJECT_AI_PACKET) +#define get_ai(cr) has_trait(TRAIT_OBJECT,cr,OBJECT_AI_PACKET) +#define set_ai(cr,ai) critter_add_trait(cr,TRAIT_OBJECT,OBJECT_AI_PACKET,ai) +#define set_self_ai(ai) set_ai(self_obj,ai) + +// visibility #define self_visible obj_is_visible_flag(self_obj) +#define set_obj_invisible(cr) set_obj_visibility(cr, true) +#define set_obj_visible(cr) set_obj_visibility(cr, false) +#define set_self_invisible set_obj_visibility(self_obj, true) +#define set_self_visible set_obj_visibility(self_obj, false) +#define is_visible(cr) has_trait(TRAIT_OBJECT,cr,OBJECT_VISIBILITY) // aka obj_is_visible_flag(x) +// hp #define self_cur_hits (get_critter_stat(self_obj,STAT_current_hp)) #define self_max_hits (get_critter_stat(self_obj,STAT_max_hp)) +#define get_cur_hits(cr) get_critter_stat(cr,STAT_current_hp) +#define get_max_hits(cr) get_critter_stat(cr,STAT_max_hp) #define self_mental_block (has_trait(TRAIT_PERK,self_obj,PERK_mental_block)) #define self_presence (has_trait(TRAIT_PERK,self_obj,PERK_presence)) @@ -162,12 +210,14 @@ #define stat_success(x,y,z) (is_success(do_check(x,y,z))) #define self_can_see_dude obj_can_see_obj(self_obj,dude_obj) +#define self_can_hear_dude obj_can_hear_obj(self_obj,dude_obj) #define self_distance_from_dude tile_distance(self_tile, dude_tile) #define self_is_high drug_influence(self_obj) #define self_item(x) obj_carrying_pid_obj(self_obj, x) #define self_item_count(x) obj_is_carrying_obj_pid(self_obj, x) - +#define get_item(cr,pid) obj_carrying_pid_obj(cr,pid) +#define get_item_count(cr,pid) obj_is_carrying_obj_pid(cr,pid) // some timer event macros #define check_set_obj_visiblility(the_obj, x) if (obj_is_visible_flag(the_obj) == x) then set_obj_visibility(the_obj, x) diff --git a/artifacts/scripting/headers/define_extra.h b/artifacts/scripting/headers/define_extra.h index 0e916e325..02cbe1860 100644 --- a/artifacts/scripting/headers/define_extra.h +++ b/artifacts/scripting/headers/define_extra.h @@ -35,7 +35,7 @@ #define WEAPON_TYPE_UNARMED (1) #define WEAPON_TYPE_MELEE (2) #define WEAPON_TYPE_THROWN (3) -#define WEAPON_TYPE_GUNS (4) +#define WEAPON_TYPE_RANGED (4) /* Item Flags (FlagsExt in proto) */ #define HEALING_ITEM 0x04000000 // Healing Item (item will be used by NPCs for healing in combat) [sfall 4.3.1/3.8.31] @@ -49,24 +49,38 @@ #define WEAPON_2HAND 0x00000200 // 2Hnd (weapon is two-handed) #define WEAPON_ENERGY 0x00000400 // Energy Weapon (forces weapon to use Energy Weapons skill) [sfall 4.2/3.8.20] -#define ATKMODE_PRI_NONE 0 -#define ATKMODE_PRI_PUNCH 1 // 0001 -#define ATKMODE_PRI_KICK 2 // 0010 -#define ATKMODE_PRI_SWING 3 // 0011 -#define ATKMODE_PRI_THRUST 4 // 0100 -#define ATKMODE_PRI_THROW 5 // 0101 -#define ATKMODE_PRI_SINGLE 6 // 0110 -#define ATKMODE_PRI_BURST 7 // 0111 -#define ATKMODE_PRI_FLAME 8 // 1000 -#define ATKMODE_SEC_NONE 0 -#define ATKMODE_SEC_PUNCH 16 // 0x00000010 -#define ATKMODE_SEC_KICK 32 // 0x00000020 -#define ATKMODE_SEC_SWING 48 // 0x00000030 -#define ATKMODE_SEC_THRUST 64 // 0x00000040 -#define ATKMODE_SEC_THROW 80 // 0x00000050 -#define ATKMODE_SEC_SINGLE 96 // 0x00000060 -#define ATKMODE_SEC_BURST 112 // 0x00000070 -#define ATKMODE_SEC_FLAME 128 // 0x00000080 +// The attack types returned by get_attack_type or as the fifth argument of HOOK_ITEMDAMAGE +#define ATKTYPE_LWEP1 (0) +#define ATKTYPE_LWEP2 (1) +#define ATKTYPE_RWEP1 (2) +#define ATKTYPE_RWEP2 (3) +#define ATKTYPE_PUNCH (4) +#define ATKTYPE_KICK (5) +#define ATKTYPE_LWEP_RELOAD (6) +#define ATKTYPE_RWEP_RELOAD (7) +#define ATKTYPE_STRONGPUNCH (8) +#define ATKTYPE_HAMMERPUNCH (9) +#define ATKTYPE_HAYMAKER (10) +#define ATKTYPE_JAB (11) +#define ATKTYPE_PALMSTRIKE (12) +#define ATKTYPE_PIERCINGSTRIKE (13) +#define ATKTYPE_STRONGKICK (14) +#define ATKTYPE_SNAPKICK (15) +#define ATKTYPE_POWERKICK (16) +#define ATKTYPE_HIPKICK (17) +#define ATKTYPE_HOOKKICK (18) +#define ATKTYPE_PIERCINGKICK (19) + +#define ATTACK_MODE_NONE (0) +#define ATTACK_MODE_PUNCH (1) +#define ATTACK_MODE_KICK (2) +#define ATTACK_MODE_SWING (3) +#define ATTACK_MODE_THRUST (4) +#define ATTACK_MODE_THROW (5) +#define ATTACK_MODE_SINGLE (6) +#define ATTACK_MODE_BURST (7) +#define ATTACK_MODE_FLAME (8) + /* Object flags for get/set_flags */ #define FLAG_HIDDEN (0x1) @@ -126,13 +140,14 @@ #define MSGBOX_YESNO (0x10) // use YES/NO buttons instead of DONE #define MSGBOX_CLEAN (0x20) // no buttons -// Some possible defines for the 4th argument to HOOK_REMOVEINVOBJ +// Some possible defines for the fourth argument of HOOK_REMOVEINVENOBJ #define RMOBJ_ITEM_REMOVED_INVEN 4831349 // removing or destroying an item (obj_remove_from_inven_) #define RMOBJ_ITEM_REMOVED 4548572 // (op_rm_obj_from_inven_) #define RMOBJ_ITEM_REMOVED_MULTI 4563866 // (op_rm_mult_objs_from_inven_) #define RMOBJ_ITEM_DESTROYED 4543215 // (op_destroy_object_) #define RMOBJ_ITEM_DESTROY_MULTI 4571599 // (op_destroy_mult_objs_) #define RMOBJ_ITEM_MOVE 4683293 // (item_move_func_) +#define RMOBJ_ITEM_REPLACE 4686256 // (item_replace_) #define RMOBJ_CONSUME_DRUG 4666772 // (inven_action_cursor_) #define RMOBJ_USE_OBJ 4666865 // (inven_action_cursor_) #define RMOBJ_EQUIP_ARMOR 4658121 // (inven_pickup_) @@ -263,6 +278,8 @@ // critters #define PROTO_CR_FLAGS (32) // Critter Flags +#define PROTO_CR_BASE_STATS (36) // 35 stats, see STAT_* +#define PROTO_CR_BONUS_STATS (176) // 35 stats, see STAT_* #define PROTO_CR_BONUS_SRENGTH (176) #define PROTO_CR_BONUS_PRCEPTION (180) #define PROTO_CR_BONUS_ENDURANCE (184) @@ -280,6 +297,7 @@ #define PROTO_CR_BONUS_HEALING_RATE (232) #define PROTO_CR_BONUS_CRITICAL_CHANCE (236) #define PROTO_CR_BONUS_BETTER_CRITICALS (240) +#define PROTO_CR_SKILLS (316) // 18 skills, see SKILL_* #define PROTO_CR_BODY_TYPE (388) #define PROTO_CR_KILL_EXP (392) #define PROTO_CR_KILL_TYPE (396) @@ -438,6 +456,7 @@ // critters #define OBJ_DATA_COMBAT_STATE (0x3C) // flags: 1 - combat, 2 - target is out of range, 4 - flee #define OBJ_DATA_CUR_ACTION_POINT (0x40) +#define OBJ_DATA_DAMAGE_FLAGS (0x44) #define OBJ_DATA_DAMAGE_LAST_TURN (0x48) #define OBJ_DATA_WHO_HIT_ME (0x54) // current target of the critter @@ -488,6 +507,15 @@ #define C_ATTACK_KNOCKBACK_VALUE5 (0xB0) #define C_ATTACK_KNOCKBACK_VALUE6 (0xB4) +#define CRITICAL_VALUE_MULT (0) // This is divided by 2, so a value of 3 does 1.5x damage, and 8 does 4x damage. +#define CRITICAL_VALUE_EFFECTS (1) // This is a flag bit field (DAM_*) controlling what effects the critical causes. +#define CRITICAL_VALUE_STAT_CHECK (2) // This makes a check against a (SPECIAL) stat. Values of 2 (endurance), 5 (agility), and 6 (luck) are used, but other stats will probably work as well. A value of -1 indicates that no check is to be made. +#define CRITICAL_VALUE_STAT_MOD (3) // Affects the outcome of the stat check, if one is made. Positive values make it easier to pass the check, and negative ones make it harder. +#define CRITICAL_VALUE_FAIL_EFFECT (4) // Another bit field, using the same values as EFFECTS. If the stat check is failed, these are applied in addition to the earlier ones. +#define CRITICAL_VALUE_MSG (5) // The message to show when this critical occurs, taken from combat.msg. +#define CRITICAL_VALUE_FAIL_MSG (6) // This is shown instead of Message if the stat check fails. + + /* Playback mode defines for the soundplay function */ #define soundraw (0x80000000) // sfall flag #define Stereo8bit (soundstereo) diff --git a/artifacts/scripting/headers/define_lite.h b/artifacts/scripting/headers/define_lite.h index a0226d349..b267adfe1 100644 --- a/artifacts/scripting/headers/define_lite.h +++ b/artifacts/scripting/headers/define_lite.h @@ -395,6 +395,7 @@ #define SKILL_TRAPS (11) #define SKILL_SCIENCE (12) #define SKILL_REPAIR (13) +#define SKILL_SPEECH (14) #define SKILL_CONVERSANT (14) #define SKILL_BARTER (15) #define SKILL_GAMBLING (16) @@ -815,15 +816,6 @@ //Misc commands #define obj_get_rot(obj) (has_trait(TRAIT_OBJECT, obj, OBJECT_CUR_ROT)) #define obj_in_party(x) (party_member_obj(obj_pid(x)) != 0) - -#define dude_tile (tile_num(dude_obj)) -#define dude_elevation (elevation(dude_obj)) #define dude_skill(x) (has_skill(dude_obj, x)) - -// some commands -#define mstr(x) message_str(NAME,x) -#define display_mstr(x) display_msg(mstr(x)) -#define g_mstr(x) message_str(SCRIPT_GENERIC,x) - #endif // DEFINE_H diff --git a/artifacts/scripting/headers/lib.arrays.h b/artifacts/scripting/headers/lib.arrays.h index 8bee30d89..2695cc27d 100644 --- a/artifacts/scripting/headers/lib.arrays.h +++ b/artifacts/scripting/headers/lib.arrays.h @@ -35,6 +35,9 @@ procedure array_keys(variable array); // list of array values (useful for maps) procedure array_values(variable array); +// makes given array permanent and returns it +procedure array_fixed(variable array); + // returns temp array containing a subarray starting from $index with $count elements // negative $index means index from the end of array // negative $count means leave this many elements from the end of array @@ -91,6 +94,11 @@ procedure get_empty_array_index(variable array); procedure add_array_set(variable array, variable item); procedure remove_array_set(variable array, variable item); +// Creates a new array filled from a given array by transforming each value using given procedure name. +procedure array_transform(variable arr, variable valueFunc); + +// Create a new temp array filled from a given array by transforming each key and value using given procedure name. +procedure array_transform_kv(variable arr, variable keyFunc, variable valueFunc); @@ -123,7 +131,7 @@ procedure remove_array_block(variable arr, variable blocksize, variable index); /** * Converts any array to string for debugging purposes */ -procedure debug_array_str(variable arr); +#define debug_array_str(arr) debug_array_str_deep(arr, 1) #define display_array(arr) display_msg(debug_array_str(arr)) @@ -223,6 +231,11 @@ procedure array_values(variable array) begin return tmp; end +procedure array_fixed(variable array) begin + fix_array(array); + return array; +end + procedure array_slice(variable array, variable index, variable count) begin variable tmp, i, n; n := len_array(array); @@ -284,7 +297,9 @@ procedure copy_array(variable src, variable srcPos, variable dest, variable dstP end end -// create exact copy of the array as a new temp array +/** + * Creates a shallow copy of the array as a new temp array. + */ procedure clone_array(variable array) begin variable new, k, v; if (array_is_map(array)) then @@ -399,14 +414,33 @@ procedure remove_array_set(variable array, variable item) begin end end -// use callback on each array element -procedure array_map_func(variable arr, variable callback) begin - variable k, v, i; - foreach (k: v in arr) - arr[k] := callback(v); - return arr; +// Creates a new array filled from a given array by transforming each value using given procedure name. +procedure array_transform(variable arr, variable valueFunc) begin + variable k, v, retArr := temp_array_map if array_is_map(arr) else temp_array(len_array(arr), 0); + foreach (k: v in arr) begin + retArr[k] := valueFunc(v); + end + return retArr; +end + +// Create a new temp array filled from a given array by transforming each key and value using given procedure name. +procedure array_transform_kv(variable arr, variable keyFunc, variable valueFunc) begin + variable k, v, retArr := temp_array_map; + foreach (k: v in arr) begin + retArr[keyFunc(k)] := valueFunc(v); + end + return retArr; end +procedure array_to_set(variable arr) begin + variable v, retArr := temp_array_map; + foreach (v in arr) begin + retArr[v] := 1; + end + return retArr; +end + + #define ARRAY_EMPTY_INDEX (-1) /** @@ -482,6 +516,7 @@ procedure array_append(variable arr1, variable arr2) begin return arr1; end +// Loads a "saved" array. If it doesn't exist, creates it (with a given size). procedure load_create_array(variable name, variable size) begin variable arr; arr := load_array(name); @@ -492,6 +527,7 @@ procedure load_create_array(variable name, variable size) begin return arr; end +// Creates and returns a new "saved" array. If array already existed with this name, frees it. procedure get_saved_array_new(variable name, variable size) begin variable arr; arr := load_array(name); @@ -582,30 +618,45 @@ end Different utility functions... */ -procedure debug_array_str(variable arr) begin - variable i := 0, k, s, len; +procedure debug_array_str_deep(variable arr, variable levels) begin +#define _newline if (levels > 1) then s += "\n"; +#define _indent ii := 0; while (ii < levels - 1) do begin s += " "; ii++; end +#define _value(v) (v if (levels <= 1 or not array_exists(v)) else debug_array_str_deep(v, levels - 1)) + variable i := 0, ii, k, v, s, len; len := len_array(arr); if (array_is_map(arr)) then begin // print assoc array s := "Map("+len+"): {"; while i < len do begin + _newline k := array_key(arr, i); - s += k + ": "+ get_array(arr, k); + v := get_array(arr, k); + _indent + s += k + ": " + _value(v); if i < (len - 1) then s += ", "; i++; end - if (strlen(s) > 254) then s := substr(s, 0, 251) + "..."; + //if (strlen(s) > 254) then s := substr(s, 0, 251) + "..."; + _newline s += "}"; end else begin // print list s := "List("+len+"): ["; + _newline while i < len do begin - s += arr[i]; + _newline + v := get_array(arr, i); + _indent + s += _value(v); if i < (len - 1) then s += ", "; i++; end - if (strlen(s) > 254) then s := substr(s, 0, 251) + "..."; + //if (strlen(s) > 254) then s := substr(s, 0, 251) + "..."; + _newline s += "]"; end return s; +#undef _newline +#undef _indent +#undef _value end procedure _PURGE_all_saved_arrays begin diff --git a/artifacts/scripting/headers/lib.inven.h b/artifacts/scripting/headers/lib.inven.h index f010a16f9..1c757a610 100644 --- a/artifacts/scripting/headers/lib.inven.h +++ b/artifacts/scripting/headers/lib.inven.h @@ -3,6 +3,8 @@ #define LIB_INVEN_H #include "define_lite.h" +#include "define_extra.h" +#define PID_BOTTLE_CAPS (41) /** Inventory contents as temp array to be used in foreach @@ -21,10 +23,10 @@ procedure inven_as_array(variable critter) begin end -procedure add_items_pid(variable who_obj, variable the_pid, variable pid_qty) begin +procedure add_items_pid(variable invenObj, variable itemPid, variable quantity) begin variable item; - item := create_object(the_pid,0,0); - add_mult_objs_to_inven(who_obj, item, (pid_qty)); + item := create_object(itemPid, 0, 0); + add_mult_objs_to_inven(invenObj, item, quantity); return item; end @@ -35,50 +37,52 @@ end #define critter_wearing_armor(x) (obj_item_subtype(critter_inven_obj(x,INVEN_TYPE_WORN)) == item_type_armor) #endif -procedure unwield_armor(variable who_obj) begin +procedure unwield_armor(variable critter) begin variable armor; - if (not(who_obj)) then return; - if (critter_wearing_armor(who_obj)) then begin - armor := critter_inven_obj(who_obj,INVEN_TYPE_WORN); - rm_obj_from_inven(who_obj, armor); - add_obj_to_inven(who_obj, armor); + if (not(critter)) then return; + if (critter_wearing_armor(critter)) then begin + armor := critter_inven_obj(critter,INVEN_TYPE_WORN); + rm_obj_from_inven(critter, armor); + add_obj_to_inven(critter, armor); end end -procedure remove_items_pid(variable who_obj, variable the_pid, variable pid_qty) begin +procedure remove_items_pid(variable invenObj, variable itemPid, variable quantity) begin variable begin item; - removed_qty; - tmp; + toRemoveQty; + actualQty; end - if (not(who_obj)) then return; - removed_qty := obj_is_carrying_obj_pid(who_obj,the_pid); - if (pid_qty < removed_qty and pid_qty != -1) then begin - removed_qty := pid_qty; + if (not(invenObj)) then return; + actualQty := obj_is_carrying_obj_pid(invenObj, itemPid); + if (quantity > actualQty or quantity < 0) then begin + quantity := actualQty; end - if (removed_qty > 0) then begin - item := obj_carrying_pid_obj(who_obj, the_pid); - if (obj_type(who_obj) == 1) then begin - if (critter_inven_obj(who_obj,INVEN_TYPE_WORN) == item) then begin - call unwield_armor(who_obj); - end else if ((critter_inven_obj(who_obj, INVEN_TYPE_LEFT_HAND) == item) or (critter_inven_obj(who_obj, INVEN_TYPE_RIGHT_HAND) == item)) then begin - inven_unwield(who_obj); + toRemoveQty := quantity; + while (quantity > 0) do begin + item := obj_carrying_pid_obj(invenObj, itemPid); + if (obj_type(invenObj) == OBJ_TYPE_CRITTER) then begin + if (critter_inven_obj(invenObj, INVEN_TYPE_WORN) == item) then begin + call unwield_armor(invenObj); + end else if ((critter_inven_obj(invenObj, INVEN_TYPE_LEFT_HAND) == item) or (critter_inven_obj(invenObj, INVEN_TYPE_RIGHT_HAND) == item)) then begin + inven_unwield(invenObj); end end - tmp := rm_mult_objs_from_inven(who_obj, item, removed_qty); + quantity -= rm_mult_objs_from_inven(invenObj, item, quantity); destroy_object(item); end + return toRemoveQty; end -procedure remove_item_obj(variable who_obj, variable item) begin - if (obj_type(who_obj) == 1) then begin - if (critter_inven_obj(who_obj,INVEN_TYPE_WORN) == item) then begin - call unwield_armor(who_obj); - end else if ((critter_inven_obj(who_obj, INVEN_TYPE_LEFT_HAND) == item) or (critter_inven_obj(who_obj, INVEN_TYPE_RIGHT_HAND) == item)) then begin - inven_unwield(who_obj); +procedure remove_item_obj(variable invenObj, variable item) begin + if (obj_type(invenObj) == 1) then begin + if (critter_inven_obj(invenObj,INVEN_TYPE_WORN) == item) then begin + call unwield_armor(invenObj); + end else if ((critter_inven_obj(invenObj, INVEN_TYPE_LEFT_HAND) == item) or (critter_inven_obj(invenObj, INVEN_TYPE_RIGHT_HAND) == item)) then begin + inven_unwield(invenObj); end end - rm_obj_from_inven(who_obj, item); + rm_obj_from_inven(invenObj, item); destroy_object(item); end @@ -89,38 +93,21 @@ end /** Set item quantity in inventory, by item pid */ -procedure set_items_qty_pid(variable invenobj, variable itempid, variable newcount) +procedure set_items_qty_pid(variable invenobj, variable itempid, variable quantity) begin variable begin count; obj; end count := obj_is_carrying_obj_pid(invenobj, itempid); - if (newcount > count) then begin + if (quantity > count) then begin obj := create_object_sid(itempid, 0, 0, -1); - add_mult_objs_to_inven(invenobj, obj, newcount - count); - end else if (newcount < count) then begin - call remove_items_pid(invenobj, itempid, count - newcount); + add_mult_objs_to_inven(invenobj, obj, quantity - count); + end else if (quantity < count) then begin + call remove_items_pid(invenobj, itempid, count - quantity); end end -/* - -procedure check_restock_item(the_item, min_amt, max_amt, res_perc) - restock_amt := random(min_amt, max_amt); - if (obj_is_carrying_obj_pid(self_obj, the_item) < restock_amt) then begin - if (res_perc >= random(1,100)) then begin - stock_pid_qty(self_obj, the_item, restock_amt) - end - end else begin - stock_pid_qty(self_obj, the_item, restock_amt) - end -procedure check_restock_item_min_limit(the_item, min_amt, max_amt, res_perc) - if (obj_is_carrying_obj_pid(self_obj, the_item) < min_amt) then begin - check_restock_item(the_item, min_amt, max_amt, res_perc) - end -*/ - procedure reduce_merchant_loot(variable critter, variable moneyPercent, variable probArmor, variable probDrugs, variable probWeapons, variable probAmmo, variable probMisc) begin variable inv, item, it, prob, tmp; diff --git a/artifacts/scripting/headers/lib.math.h b/artifacts/scripting/headers/lib.math.h index f649c4fd0..5101828ab 100644 --- a/artifacts/scripting/headers/lib.math.h +++ b/artifacts/scripting/headers/lib.math.h @@ -5,53 +5,36 @@ Numbers... */ -#define above(a, b) (unsigned_comp(a, b) > 0) -#define above_equal(a, b) (unsigned_comp(a, b) >= 0) -#define below(a, b) (unsigned_comp(a, b) < 0) -#define below_equal(a, b) (unsigned_comp(a, b) <= 0) - // for sfall 4.2.3/3.8.23 -pure procedure unsigned_comp(variable a, variable b) begin +pure procedure unsigned_int_compare(variable a, variable b) begin if ((a bwxor b) == 0) then return 0; // a == b return 1 if ((b == 0) orElse a div b) else -1; end -#define MAX(x, y) ((x > y) * x + (x <= y) * y) -#define MIN(x, y) ((x < y) * x + (x >= y) * y) -#define in_range(x, from, to) (x >= from and x <= to) +#define MAX(x, y) ((x > y) * x + (x <= y) * y) +#define MIN(x, y) ((x < y) * x + (x >= y) * y) -procedure max(variable x, variable y) begin - if (x > y) then return x; - return y; -end +#define math_max(x, y) (x if x > y else y) +#define math_min(x, y) (x if x < y else y) +#define math_in_range(x, from, to) (x >= from and x <= to) -procedure min(variable x, variable y) begin - if (x < y) then return x; - return y; -end -/*procedure round(variable val) begin - variable intp; - intp := floor(val); - if ((val-intp) >= 0.5) then intp++; - return intp; -end*/ - -/*procedure ceil(variable val) begin - variable intp; - intp := floor(val); - if (abs(val-intp) > 0.0) then begin - intp++; +procedure math_clamp(variable val, variable a, variable b) begin + variable min, max; + if (a < b) then begin + min := a; + max := b; + end else begin + min := b; + max := a; end - return intp; -end*/ - -procedure cap_number(variable num, variable min, variable max) begin - if (num > max) then num := max; - else if (num < min) then num := min; - return num; + if (val < min) then + return min; + else if (val > max) then + return max; + else + return val; end - #endif diff --git a/artifacts/scripting/headers/lib.misc.h b/artifacts/scripting/headers/lib.misc.h index b730931bf..c683e671e 100644 --- a/artifacts/scripting/headers/lib.misc.h +++ b/artifacts/scripting/headers/lib.misc.h @@ -59,27 +59,17 @@ procedure hotkey_pressed_now(variable n, variable key) begin end end - - -/** - Attempt to make list_as_array safe -*/ -/*procedure list_as_array_safe(variable type) begin - variable list, item, arr, i; - list := list_begin(type); - arr := temp_array(100, 4); - i := 0; - item := list_next(list); - while (item) do begin - if (len_array(arr) == i) then resize_array(arr, len_array(arr) + 100); - arr[i] := item; - item := list_next(list); - i++; +// Loads ini section as map of keys and values parsed as integers (0 values will be skipped!) +procedure get_ini_section_int_to_int(variable file, variable section, variable fixArray := false) begin + variable ar, ar2 := temp_array_map, k, v; + ar := get_ini_section(file, section); + foreach k: v in ar begin + ar2[atoi(k)] := atoi(v); end - resize_array(arr, i); - list_end(list); - return arr; -end*/ + if (fixArray) then + fix_array(ar2); + return ar2; +end #endif diff --git a/artifacts/scripting/headers/lib.obj.h b/artifacts/scripting/headers/lib.obj.h new file mode 100644 index 000000000..2d33d25ba --- /dev/null +++ b/artifacts/scripting/headers/lib.obj.h @@ -0,0 +1,12 @@ +#ifndef LIB_OBJ_H +#define LIB_OBJ_H + +#include "define_lite.h" +#include "define_extra.h" + +#define obj_name_safe(obj) (obj_name(obj) if obj else "(null)") +#define get_active_weapon(critter) critter_inven_obj(critter, (2 - active_hand) if critter == dude_obj else INVEN_TYPE_RIGHT_HAND) +#define obj_has_lof_to_obj(critter, target) (obj_blocking_line(critter, tile_num(target), BLOCKING_TYPE_SHOOT) == target) +#define obj_can_hear_and_shoot_obj(critter, target) (obj_can_hear_obj(critter, target) and obj_has_lof_to_obj(critter, target)) + +#endif diff --git a/artifacts/scripting/headers/lib.strings.h b/artifacts/scripting/headers/lib.strings.h index 1b07a9f22..a1f6ce208 100644 --- a/artifacts/scripting/headers/lib.strings.h +++ b/artifacts/scripting/headers/lib.strings.h @@ -9,19 +9,14 @@ #ifndef LIB_STRINGS_H #define LIB_STRINGS_H -#define is_in_string(str, substr) (string_pos(str, substr) != -1) -#define string_starts_with(str, substr) (string_pos(str, substr) == 0) +#include "sfall.h" -/** - * Returns position of first occurance of substr in str, or -1 if not found - */ -procedure string_pos(variable str, variable subst) begin - variable lst, n; - lst := string_split(str, subst); - if (len_array(lst) < 2) then - return -1; - return strlen(lst[0]); -end +#define string_contains(str, sub) (string_find(str, sub) != -1) +#define string_starts_with(str, sub) (substr(str, 0, strlen(sub)) == sub) +#define string_format_array(fmt, arr) sprintf_array(fmt, arr) +// Replaces all occurances of substring in a string with another substring +#define string_replace(str, search, replace) (string_join(string_split(str, search), replace)) +#define sprintf2(fmt, arg1, arg2) string_format2(fmt, arg1, arg2) /** * Join array of strings using delimeter @@ -39,44 +34,6 @@ procedure string_join(variable array, variable join) begin return str; end -/** - * replaces all occurances of substring in a string with another substring - */ -#define string_replace(str, search, replace) (string_join(string_split(str, search), replace)) - -/** - * sprintf with two arguments - */ -procedure sprintf2(variable str, variable arg1, variable arg2) begin - variable split, len, i, j, arg; - split := string_split(str, "%"); - len := len_array(split); - str := ""; - if (len > 0) then begin - str := split[0]; - j := 0; - for (i := 1; i < len; i++) begin - if (split[i] == "") then begin - if (i < len - 1) then begin - str += "%" + split[i+1]; - i++; - end - end else begin - if (j == 0) then - arg := arg1; - else if (j == 1) then - arg := arg2; - else - arg := 0; - - str += sprintf("%" + split[i], arg); - j++; - end - end - end - return str; -end - /** * sprintf with unlimited number of arguments */ @@ -149,35 +106,48 @@ procedure string_repeat(variable str, variable count) begin return out; end -/** - * Workaround for string_split bug in sfall 3.3 - * DEPRECATED as of sfall 3.4 (bug fixed) - */ -procedure string_split_safe(variable str, variable split) begin - variable lst; - str += split; - lst := string_split(str, split); - resize_array(lst, len_array(lst) - 1); - return lst; -end - /** * The same as sfall string_split, but returns array of integers instead * Useful in cunjunction with is_in_array() */ procedure string_split_ints(variable str, variable split) begin - variable i := 0; - variable list; + variable n := 0; + variable list, result, val; + + if (str == "" or typeof(str) != VALTYPE_STR) then + return temp_array_list(0); + list := string_split(str, split); - while (i < len_array(list)) do begin - list[i] := atoi(list[i]); - i++; + result := temp_array_list(0); + foreach (val in list) begin + if (val != "") then begin + resize_array(result, n + 1); + result[n] := atoi(val); + n++; + end end - return list; + return result; +end + +// atoi proc wrapper, for use as callback +procedure string_to_int(variable str) begin + return atoi(str); +end + +// atof proc wrapper, for use as callback +procedure string_to_float(variable str) begin + return atof(str); +end + +// converts any value to a string, for use as callback +procedure to_string(variable val) begin + return ""+val; end /** + DEPRECATED, use string_format instead! + String parse functions. Idea taken from KLIMaka on TeamX forums. Placeholders in format %d% are replaced from string. d refers to variable index (starting from 1). You can repeat one placeholder multiple times, or use placeholders in any order. diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index d94714537..2167b639d 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -3,6 +3,7 @@ // Recognised modes for set_shader_mode and get_game_mode #define WORLDMAP (0x1) +#define LOCALMAP (0x2) //No point hooking this: would always be 1 at any point at which scripts are running #define DIALOG (0x4) #define ESCMENU (0x8) #define SAVEGAME (0x10) @@ -24,7 +25,7 @@ #define COUNTERWIN (0x100000) // counter window for moving multiple items or setting a timer #define SPECIAL (0x80000000) -// Valid arguments to register_hook +// Valid arguments to register_hook_proc #define HOOK_TOHIT (0) #define HOOK_AFTERHITROLL (1) #define HOOK_CALCAPCOST (2) @@ -103,28 +104,6 @@ #define ENCOUNTER_FLAG_ICON_SP (0x8) // use special encounter icon #define ENCOUNTER_FLAG_FADEOUT (0x10) // fade out the screen on encounter (Note: you yourself should restore the fade screen when entering the encounter) -// The attack types returned by get_attack_type -#define ATKTYPE_LWEP1 (0) -#define ATKTYPE_LWEP2 (1) -#define ATKTYPE_RWEP1 (2) -#define ATKTYPE_RWEP2 (3) -#define ATKTYPE_PUNCH (4) -#define ATKTYPE_KICK (5) -#define ATKTYPE_LWEP_RELOAD (6) -#define ATKTYPE_RWEP_RELOAD (7) -#define ATKTYPE_STRONGPUNCH (8) -#define ATKTYPE_HAMMERPUNCH (9) -#define ATKTYPE_HAYMAKER (10) -#define ATKTYPE_JAB (11) -#define ATKTYPE_PALMSTRIKE (12) -#define ATKTYPE_PIERCINGSTRIKE (13) -#define ATKTYPE_STRONGKICK (14) -#define ATKTYPE_SNAPKICK (15) -#define ATKTYPE_POWERKICK (16) -#define ATKTYPE_HIPKICK (17) -#define ATKTYPE_HOOKKICK (18) -#define ATKTYPE_PIERCINGKICK (19) - // Return values for "typeof" #define VALTYPE_NONE (0) // not used yet #define VALTYPE_INT (1) @@ -170,7 +149,7 @@ // sort map in descending order by key value #define sort_map_reverse(array) resize_array(array, -7) // remove element from map or just replace value with 0 for list -#define unset_array(array, item) set_array(array, item, 0) +#define unset_array(array, key) set_array(array, key, 0) // same as "key_pressed" but checks VK codes instead of DX codes #define key_pressed_vk(key) (key_pressed(key bwor 0x80000000)) @@ -292,9 +271,19 @@ #define set_weapon_usable(item) set_object_data(item, OBJ_DATA_MISC_FLAGS, get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0xFFFFFFEF) #define weapon_is_unusable(item) (get_object_data(item, OBJ_DATA_MISC_FLAGS) bwand 0x00000010) +#define weapon_attack_mode1(pid) (get_proto_data(pid, PROTO_FLAG_EXT) bwand 0x0000000F) +#define weapon_attack_mode2(pid) ((get_proto_data(pid, PROTO_FLAG_EXT) bwand 0x000000F0) / 0x10) +#define weapon_attack_mode(pid, attackType) (weapon_attack_mode1(pid) if (attackType == ATKTYPE_LWEP1 or attackType == ATKTYPE_RWEP1) else weapon_attack_mode2(pid)) + +#define get_tile_fid_ext(tile, elev, mode) get_tile_fid(((mode bwand 0xF) * 0x10000000) bwor ((elev bwand 0xF) * 0x1000000) bwor (tile bwand 0xFFFFFF)) +#define get_tile_ground_fid(tile, elev) get_tile_fid_ext(tile, elev, 0) +#define get_tile_roof_fid(tile, elev) get_tile_fid_ext(tile, elev, 1) + /* SFALL_FUNCX MACROS */ +#define FUNC_SELECTOR_7(_1,_2,_3,_4,_5,_6,_7,FUNC,...) FUNC + #define add_extra_msg_file(name) sfall_func1("add_extra_msg_file", name) #define add_global_timer_event(time, fixedParam) sfall_func2("add_g_timer_event", time, fixedParam) #define add_iface_tag sfall_func0("add_iface_tag") @@ -315,10 +304,13 @@ #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) +#define get_combat_free_move sfall_func0("get_combat_free_move") #define get_current_inven_size(obj) sfall_func1("get_current_inven_size", obj) #define get_current_terrain_name sfall_func0("get_terrain_name") #define get_cursor_mode sfall_func0("get_cursor_mode") #define get_flags(obj) sfall_func1("get_flags", obj) +#define get_ini_config(file) sfall_func2("get_ini_config", file, 0) +#define get_ini_config_db(file) sfall_func2("get_ini_config", file, 1) #define get_ini_section(file, sect) sfall_func2("get_ini_section", file, sect) #define get_ini_sections(file) sfall_func1("get_ini_sections", file) #define get_interface_rect(winType) sfall_func2("get_window_attribute", winType, -1) @@ -337,7 +329,6 @@ #define get_npc_stat_max(stat) sfall_func2("get_stat_max", stat, 1) #define get_npc_stat_min(stat) sfall_func2("get_stat_min", stat, 1) #define get_sfall_arg_at(argNum) sfall_func1("get_sfall_arg_at", argNum) -#define get_string_pointer(text) sfall_func1("get_string_pointer", text) #define get_terrain_name(x, y) sfall_func2("get_terrain_name", x, y) #define get_text_width(text) sfall_func1("get_text_width", text) #define has_fake_perk_npc(npc, perk) sfall_func2("has_fake_perk_npc", npc, perk) @@ -377,6 +368,7 @@ #define remove_timer_event(fixedParam) sfall_func1("remove_timer_event", fixedParam) #define set_can_rest_on_map(map, elev, value) sfall_func3("set_can_rest_on_map", map, elev, value) #define set_car_intface_art(artIndex) sfall_func1("set_car_intface_art", artIndex) +#define set_combat_free_move(value) sfall_func1("set_combat_free_move", value) #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) @@ -390,6 +382,7 @@ #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_scr_name(name) sfall_func1("set_scr_name", name) +#define set_spray_settings(ctrMult, ctrDiv, tgtMult, tgtDiv) sfall_func4("set_spray_settings", ctrMult, ctrDiv, tgtMult, tgtDiv) #define set_terrain_name(x, y, name) sfall_func3("set_terrain_name", x, y, name) #define set_town_title(areaID, title) sfall_func2("set_town_title", areaID, title) #define set_unique_id(obj) sfall_func1("set_unique_id", obj) @@ -400,7 +393,16 @@ #define spatial_radius(obj) sfall_func1("spatial_radius", obj) #define string_compare(str1, str2) sfall_func2("string_compare", str1, str2) #define string_compare_locale(str1, str2, codePage) sfall_func3("string_compare", str1, str2, codePage) -#define string_format(format, a1, a2) sfall_func3("string_format", format, a1, a2) +#define string_find(haystack, needle) sfall_func2("string_find", haystack, needle) +#define string_find_from(haystack, needle, pos) sfall_func3("string_find", haystack, needle, pos) +#define string_format1(format, a1) sfall_func2("string_format", format, a1) +#define string_format2(format, a1, a2) sfall_func3("string_format", format, a1, a2) +#define string_format3(format, a1, a2, a3) sfall_func4("string_format", format, a1, a2, a3) +#define string_format4(format, a1, a2, a3, a4) sfall_func5("string_format", format, a1, a2, a3, a4) +#define string_format5(format, a1, a2, a3, a4, a5) sfall_func6("string_format", format, a1, a2, a3, a4, a5) +#define string_format6(format, a1, a2, a3, a4, a5, a6) sfall_func7("string_format", format, a1, a2, a3, a4, a5, a6) +#define string_format7(format, a1, a2, a3, a4, a5, a6, a7) sfall_func8("string_format", format, a1, a2, a3, a4, a5, a6, a7) +#define string_format(format, ...) FUNC_SELECTOR_7(__VA_ARGS__,string_format7,string_format6,string_format5,string_format4,string_format3,string_format2,string_format1)(format, __VA_ARGS__) #define string_tolower(text) sfall_func2("string_to_case", text, 0) #define string_toupper(text) sfall_func2("string_to_case", text, 1) #define tile_by_position(x, y) sfall_func2("tile_by_position", x, y) diff --git a/artifacts/scripting/hooks.yml b/artifacts/scripting/hooks.yml index 7f47fa4f0..a618b4184 100644 --- a/artifacts/scripting/hooks.yml +++ b/artifacts/scripting/hooks.yml @@ -23,7 +23,7 @@ int arg6 - Ranged flag. 1 if the hit chance calculation takes into account the distance to the target. This does not mean the attack is a ranged attack int arg7 - The raw hit chance before applying the cap - int ret0 - the new hit chance + int ret0 - The new hit chance. The value is limited to the range of -99 to 999 ``` - name: AfterHitRoll @@ -227,7 +227,7 @@ Critter arg1 - the critter being bartered with int arg2 - the default value of the goods Critter arg3 - table of requested goods (being bought from NPC) - int arg4 - the amount of actual caps in the barter stack (as opposed to goods) + int arg4 - the number of actual caps in the barter stack (as opposed to goods) int arg5 - the value of all goods being traded before skill modifications Critter arg6 - table of offered goods (being sold to NPC) int arg7 - the total cost of the goods offered by the player @@ -296,8 +296,10 @@ id: HOOK_AMMOCOST doc: | Runs when calculating ammo cost for a weapon. Doesn't affect damage, only how much ammo is spent.
- By default, weapons can make attacks when at least 1 ammo is left, regardless of ammo cost calculations.
- To add proper check for ammo before attacking and proper calculation of the number of burst rounds (hook type 1 and 2 in `arg3`), set **CheckWeaponAmmoCost=1** in **Misc** section of ddraw.ini. + By default, a weapon can perform an attack with at least one ammo, regardless of ammo cost calculation.
+ To add proper checks for ammo before attacking (hook type 1 `arg3`), set **CheckWeaponAmmoCost=1** in **Misc** section of ddraw.ini. + + __NOTE:__ The return value must be greater than or equal to 0 to be valid. ``` Item arg0 - The weapon @@ -371,10 +373,12 @@ ``` Critter arg0 - Thief Obj arg1 - The target - Item arg2 - Item being stolen/planted + Item arg2 - The item being stolen/planted int arg3 - 0 when stealing, 1 when planting + int arg4 - quantity of the item being stolen/planted - int ret0 - overrides hard-coded handler (1 - force success, 0 - force fail, -1 - use engine handler) + int ret0 - overrides hard-coded handler (2 - force fail without closing window, 1 - force success, 0 - force fail, -1 - use engine handler) + int ret1 - overrides experience points gained for stealing this item (must be greater than or equal to 0) ``` - name: WithinPerception @@ -496,9 +500,12 @@ NOTE: this hook will not be executed for `start`, `critter_p_proc`, `timed_event_p_proc`, and `map_update_p_proc` procedures. ``` - int arg0 - the number of the standard script handler (see define.h) + int arg0 - the number of the standard script handler (see *_proc in define.h) Obj arg1 - the object that owns this handler (self_obj) Obj arg2 - the object that called this handler (source_obj, can be 0) + int arg3 - always 0 (1 for _END version) + Obj arg4 - the object that is acted upon by this handler (target_obj, can be 0) + int arg5 - the parameter of this call (fixed_param), useful for combat_proc int ret0 - pass -1 to cancel the execution of the handler ``` @@ -510,10 +517,12 @@ NOTE: this hook will not be executed for `start`, `critter_p_proc`, `timed_event_p_proc`, and `map_update_p_proc` procedures. ``` - int arg0 - the number of the standard script handler (see define.h) + int arg0 - the number of the standard script handler (see *_proc in define.h) Obj arg1 - the object that owns this handler (self_obj) Obj arg2 - the object that called this handler (source_obj, can be 0) - int arg3 - 1 after procedure execution + int arg3 - always 1 (procedure end) + Obj arg4 - the object that is acted upon by this handler (target_obj, can be 0) + int arg5 - the parameter of this call (fixed_param), useful for combat_proc ``` - name: CarTravel @@ -600,10 +609,12 @@ Does not run if the script of the object overrides the description. + __NOTE:__ Returning a pointer to the new text received from the `get_string_pointer` function is still valid, but the method is DEPRECATED and is left for backward compatibility only. + ``` Obj arg0 - the object - int ret0 - a pointer to the new text received by using the get_string_pointer function + String ret0 - the new description text to use ``` - name: UseSkillOn @@ -802,7 +813,7 @@ Critter arg0 - the critter doing the check Item arg1 - the weapon being checked int arg2 - attack type (see ATKTYPE_* constants) - int arg3 - original result of engine function: 1 - can use, 0 - can't use + int arg3 - original result of engine function: 1 - can use, 0 - cannot use int ret0 - overrides the result of engine function. Any non-zero value allows using the weapon ``` diff --git a/artifacts/scripting/hookscripts.md b/artifacts/scripting/hookscripts.md index cc6935571..092be156f 100644 --- a/artifacts/scripting/hookscripts.md +++ b/artifacts/scripting/hookscripts.md @@ -108,7 +108,7 @@ int arg5 - Attack Type (see ATKTYPE_* constants) int arg6 - Ranged flag. 1 if the hit chance calculation takes into account the distance to the target. This does not mean the attack is a ranged attack int arg7 - The raw hit chance before applying the cap -int ret0 - the new hit chance +int ret0 - The new hit chance. The value is limited to the range of -99 to 999 ``` ------------------------------------------- @@ -322,7 +322,7 @@ Critter arg0 - the critter doing the bartering (either dude_obj or inven_dude) Critter arg1 - the critter being bartered with int arg2 - the default value of the goods Critter arg3 - table of requested goods (being bought from NPC) -int arg4 - the amount of actual caps in the barter stack (as opposed to goods) +int arg4 - the number of actual caps in the barter stack (as opposed to goods) int arg5 - the value of all goods being traded before skill modifications Critter arg6 - table of offered goods (being sold to NPC) int arg7 - the total cost of the goods offered by the player @@ -394,8 +394,10 @@ int ret1 - The new maximum damage #### `HOOK_AMMOCOST (hs_ammocost.int)` Runs when calculating ammo cost for a weapon. Doesn't affect damage, only how much ammo is spent.\ -By default, weapons can make attacks when at least 1 ammo is left, regardless of ammo cost calculations.\ -To add proper check for ammo before attacking and proper calculation of the number of burst rounds (hook type 1 and 2 in `arg3`), set **CheckWeaponAmmoCost=1** in **Misc** section of ddraw.ini. +By default, a weapon can perform an attack with at least one ammo, regardless of ammo cost calculation.\ +To add proper checks for ammo before attacking (hook type 1 in `arg3`), set **CheckWeaponAmmoCost=1** in **Misc** section of ddraw.ini. + +__NOTE:__ The return value must be greater than or equal to 0 to be valid. ``` Item arg0 - The weapon @@ -474,10 +476,12 @@ Example message (vanilla behavior):\ ``` Critter arg0 - Thief Obj arg1 - The target -Item arg2 - Item being stolen/planted +Item arg2 - The item being stolen/planted int arg3 - 0 when stealing, 1 when planting +int arg4 - quantity of the item being stolen/planted -int ret0 - overrides hard-coded handler (1 - force success, 0 - force fail, -1 - use engine handler) +int ret0 - overrides hard-coded handler (2 - force fail without closing window, 1 - force success, 0 - force fail, -1 - use engine handler) +int ret1 - overrides experience points gained for stealing this item (must be greater than or equal to 0) ``` ------------------------------------------- @@ -689,10 +693,12 @@ An example usage would be to add an additional description to the item based on Does not run if the script of the object overrides the description. +__NOTE:__ Returning a pointer to the new text received from the `get_string_pointer` function is still valid, but the method is DEPRECATED and is left for backward compatibility only. + ``` Obj arg0 - the object -int ret0 - a pointer to the new text received by using the get_string_pointer function +String ret0 - the new description text to use ``` ------------------------------------------- @@ -800,10 +806,12 @@ Runs before or after Fallout engine executes a standard procedure (handler) in a __NOTE:__ This hook will not be executed for `start`, `critter_p_proc`, `timed_event_p_proc`, and `map_update_p_proc` procedures. ``` -int arg0 - the number of the standard script handler (see define.h) +int arg0 - the number of the standard script handler (see *_proc in define.h) Obj arg1 - the object that owns this handler (self_obj) Obj arg2 - the object that called this handler (source_obj, can be 0) int arg3 - 1 after procedure execution (for HOOK_STDPROCEDURE_END), 0 otherwise +Obj arg4 - the object that is acted upon by this handler (target_obj, can be 0) +int arg5 - the parameter of this call (fixed_param), useful for combat_proc int ret0 - pass -1 to cancel the execution of the handler (only for HOOK_STDPROCEDURE) ``` @@ -922,7 +930,7 @@ For the player, this happens when the game updates the item data for active item Critter arg0 - the critter doing the check Item arg1 - the item being checked int arg2 - attack type (see ATKTYPE_* constants), or -1 for dude_obj -int arg3 - original result of engine function: 1 - can use, 0 - can't use +int arg3 - original result of engine function: 1 - can use, 0 - cannot use int ret0 - overrides the result of engine function. Any non-zero value allows using the weapon ``` diff --git a/artifacts/scripting/sfall function notes.md b/artifacts/scripting/sfall function notes.md index 2aabf4798..a39a9a2e1 100644 --- a/artifacts/scripting/sfall function notes.md +++ b/artifacts/scripting/sfall function notes.md @@ -18,13 +18,13 @@ Both `set_global_script_repeat` and `set_global_script_type` only have an effect The `read_xxx` functions take a memory address as the parameter and can read arbitrary pieces of Fallout's address space. The `write_xxx` functions are equivalent except that they write to arbitrary memory addresses. The `call_offset_xx` functions can be used to call arbitrary functions inside Fallout. Different versions are used to call functions with different numbers of arguments. -None of `write_xxx` and `call_offset_xx` functions will work unless **AllowUnsafeScripting** is enabled in ddraw.ini. +None of `write_xxx` and `call_offset_xx` functions will work unless **AllowUnsafeScripting** is enabled in **ddraw.ini**. The `get_pc_base_stat`, `set_pc_base_stat`, `get_pc_extra_stat` and `set_pc_extra_stat` functions are equivalent to calling `get_critter_base_stat`, `set_critter_base_stat`, `get_critter_extra_stat` and `set_critter_extra_stat` with `dude_obj` as the critter pointer. None of these stat functions take perks into account, and neither do they do range clamping to make sure the stats are valid. Use the normal `get_critter_stat` function to get a correctly perk adjusted and range clamped value for a stat. The `set_stat_min` and `set_stat_max` functions can be used to set the valid ranges on stats. Values returned by `get_current_stat` will be clamped to this range. The `set_pc_stat_*` functions only affect the player, the `set_npc_stat_*` functions only affect other critters, and the `set_stat_*` functions affect both. -The input functions are only available if the user has the input hook turned on in ddraw.ini. Use `input_funcs_available` to check. +The input functions are only available if the user has the input hook turned on in **ddraw.ini**. Use `input_funcs_available` to check. The graphics functions are only available if the user is using graphics mode 4 or 5. Use `graphics_funcs_available` to check; it returns 1 if you can use them or 0 if you can't. Calling graphics functions when `graphics_funcs_available` returns 0 will do nothing. @@ -54,11 +54,11 @@ The `has_fake_trait` and `has_fake_perk` return the number of levels the player The `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. The `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. The `set_perkbox_title` can be used to change the title of the box, or by using `""` it will be set back to the default. The `hide_real_perks` and `show_real_perks` can be used to prevent the dialog from displaying any of the original 119 perks. The `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. The `clear_selectable_perks` restores the dialog to its default state. -The `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/3.8.12, `is_iface_tag_active` can also be used to check 0 for **SNEAK**, 1 for **POISONED**, and 2 for **RADIATED**. +The `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/3.8.12, `is_iface_tag_active` can also be used to check 0 for **SNEAK**, 1 for **POISONED**, and 2 for **RADIATED**. -The `get_bodypart_hit_modifier` and `set_bodypart_hit_modifier` alter the hit percentage modifiers for aiming at specific bodyparts. Valid bodypart id's are from 0 to 8. Changes are not saved, and will reset to the defaults (or to the values specified in ddraw.ini if they exist) at each reload. +The `get_bodypart_hit_modifier` and `set_bodypart_hit_modifier` alter the hit percentage modifiers for aiming at specific bodyparts. Valid bodypart id's are from 0 to 8. Changes are not saved, and will reset to the defaults (or to the values specified in **ddraw.ini** if they exist) at each reload. -The `get_critical_table`, `set_critical_table` and `reset_critical_table` are used for modifying the critical table (for details, see http://falloutmods.wikia.com/wiki/Critical_hit_tables). Changes are not saved, and will reset to the defaults (or to the contents of **CriticalOverrides.ini**, if it exists) at each game reload. These function also require **OverrideCriticalTable** to be enabled in ddraw.ini. +The `get_critical_table`, `set_critical_table` and `reset_critical_table` are used for modifying the critical table (for details, see http://falloutmods.wikia.com/wiki/Critical_hit_tables). Changes are not saved, and will reset to the defaults (or to the contents of **CriticalOverrides.ini**, if it exists) at each game reload. These function also require **OverrideCriticalTable** to be enabled in **ddraw.ini**. The `get_unspent_ap_bonus` and `set_unspent_ap_bonus` alter the AC bonus you receive per unused action point at the end of your turn in combat. To allow for fractional values, the value given if divided by 4. (Hence the default value is 4 and not 1.) The `get_unspent_ap_perk_bonus` and `set_unspent_ap_perk_bonus` are similar, but affect the extra AC granted by the HtH Evade perk. (The default value of this is also 4, equivalent to doubling the original bonus.) @@ -70,7 +70,7 @@ The `get_proto_data` and `set_proto_data` are used to manipulate the in memory c The `list_xxx` functions can be used to loop over all items on a map. `list_begin` takes an argument telling sfall what you want to list (defined in **sfall.h**). It returns a list pointer, which you iterate through with `list_next`. Finally, when you've finished with the list use `list_end` on it. Not calling `list_end` will result in a memory leak. Alternatively, use `list_as_array` to get the whole list at once as a temp array variable, which can be looped over using `len_array` and which you don't need to remember to free afterwards. -The `play_sfall_sound` and `stop_sfall_sound` are used to play **mp3/wav/wma** files. The path given is relative to the Fallout folder. Specify mode as 1 to loop the file continuously, 2 to replace the current background game music with playing the specified file in loop mode, or 0 to play the file once. If you don't wish to loop, `play_sfall_sound` returns 0. If you do loop, it returns an id which can be passed back to `stop_sfall_sound` when you want to stop the effect. All sounds effects will be stopped on game reload, looping or not. These functions do not require **AllowDShowSound** to be set to 1 in ddraw.ini. +The `play_sfall_sound` and `stop_sfall_sound` are used to play **mp3/wav/wma** files. The path given is relative to the Fallout folder. Specify mode as 1 to loop the file continuously, 2 to replace the current background game music with playing the specified file in loop mode, or 0 to play the file once. If you don't wish to loop, `play_sfall_sound` returns 0. If you do loop, it returns an id which can be passed back to `stop_sfall_sound` when you want to stop the effect. All sounds effects will be stopped on game reload, looping or not. These functions do not require **AllowDShowSound** to be set to 1 in **ddraw.ini**. Starting from sfall 4.2.8/3.8.28, you can pass a value in the **mode** argument for a reduced sound volume. To set the volume, You need to convert the number to hexadecimal and use the argument format `0xZZZZ000Y`, where `ZZZZ` is the volume reduction value in range from 0 to 32767 (the value 32767 is muted), and `Y` is the playback mode. Arrays are created and manipulated with the `xxx_array` functions. An array must first be created with `create_array` or `temp_array`, specifying how many data elements the array can hold. You can store any of ints, floats and strings in an array, and can mix all 3 in a single array. The ID returned by `create_array` or `temp_array` can then be used with the other array functions. Arrays are shared between all scripts. (i.e. you can call `create_array` from one script, and then use the returned ID from another script.) They are also saved across savegames. Arrays created with `temp_array` will be automatically freed at the end of the frame. These functions are safe, in that supplying a bad id or trying to access out of range elements will not crash the script. `create_array` is the only function that returns a permanent array, all other functions which return arrays (`string_split`, `list_as_array`, etc,) all return temp arrays. You can use `fix_array` to make a temp array permanent.\ @@ -124,14 +124,17 @@ FUNCTION REFERENCE ----- ##### `int get_ini_setting(string setting)` - Reads an integer value from an ini file in the Fallout directory. -- It only takes a single argument; seperate the file name, section and key with a `|` character, e.g. `myvar := get_ini_setting("myini.ini|mysec|var1")`. If the file or key cannot be found, -1 is returned. +- It only takes a single argument; seperate the file name, section and key with a `|` character, e.g. `myvar := get_ini_setting("myini.ini|mysec|var1")`. +- If the file or key cannot be found or the setting argument is in an invalid format, it returns -1. - The file name is limited to 63 chars, including the extension. - The section name is limited to 32 characters. -- It can also be used to get sfall settings, by using ddraw.ini as the file name. +- It can also be used to get sfall settings by using **ddraw.ini** as the file name. ----- ##### `string get_ini_string(string setting)` - Reads a string value from an ini file in the Fallout directory. +- If the file or key cannot be found, it returns an empty string. +- If the setting argument is in an invalid format, it returns -1 (integer). ----- ##### `int get_game_mode()` @@ -167,7 +170,7 @@ FUNCTION REFERENCE ----- ##### `void set_map_time_multi(float multi)` - Adjusts how fast time passes while you're on the world map. It takes a single float as an argument, where 1 is the normal speed. -- This function works in addition to the **WorldMapTimeMod** setting in ddraw.ini and the Pathfinder perk, rather than overriding it, so calling `set_map_time_multi(0.5)` when the player has 2 levels of pathfinder would result in time passing at 25% the normal speed on the world map. +- This function works in addition to the **WorldMapTimeMod** setting in **ddraw.ini** and the Pathfinder perk, rather than overriding it, so calling `set_map_time_multi(0.5)` when the player has 2 levels of pathfinder would result in time passing at 25% the normal speed on the world map. ----- ##### `void remove_script(object obj)` @@ -226,34 +229,48 @@ FUNCTION REFERENCE ##### `void set_critter_pickpocket_mod(object critter, int max, int mod)` - The same as above, but applies only to specific critter. +----- +##### `int get_tile_fid(int tileData)` +- Returns FID information about the square under the given tile at elevation 0. +- Pass elevation as 4-bit number in bits 25-28 to access other elevations. +- Pass result mode in bits 29-32: 0 - ground FID, 1 - roof FID, 2 - raw data. Following macros are available in **sfall.h**: + +----- +##### `int get_tile_ground_fid(int tileNum, int elevation)` +- Returns FID of a ground tile at given tile number and elevation. + +----- +##### `int get_tile_roof_fid(int tileNum, int elevation)` +- Returns FID of a roof tile at given tile number and elevation. Note that FID of 1 is used when there is no actual roof. + ----- ##### `void reg_anim_combat_check` -- Allows to enable all `reg_anim_*` functions in combat (including vanilla functions) if set to 0. It is automatically reset at the end of each frame, so you need to call it before `reg_anim_begin() ... reg_anim_end()` code block. +- Allows enabling all `reg_anim_*` functions in combat (including vanilla functions) if set to 0. It is automatically reset at the end of each frame, so you need to call it before `reg_anim_begin() ... reg_anim_end()` code block. **Some additional `reg_anim_*` functions were introduced. They all work in the same convention as vanilla functions and use the same underlying code.** -##### `void reg_anim_destroy(object)` +##### `void reg_anim_destroy(object obj)` - Given object is destroyed at the end of current animation set. ----- -##### `void reg_anim_animate_and_hide(object, animID, delay)` +##### `void reg_anim_animate_and_hide(object obj, int animID, int delay)` - Exactly like `reg_anim_animate` but the object will automatically disappear after the last animation frame (but not destroyed). ----- -##### `void reg_anim_light(object, light, delay)` +##### `void reg_anim_light(object obj, int light, int delay)` - Change light of any object. light argument is a light radius (0-8), but you can use highest 2 bytes to pass light intensity as well (example: `0xFFFF0008` - intensity 65535 and radius 8). If highest 2 bytes are 0, intensity will not be changed. Intensity range is from 0 to 65535 (0xFFFF). ----- -##### `void reg_anim_change_fid(object, fid, delay)` +##### `void reg_anim_change_fid(object obj, int fid, int delay)` - Should work like `art_change_fid_num` but in `reg_anim` sequence. ----- -##### `void reg_anim_take_out(object, holdFrameID, delay)` +##### `void reg_anim_take_out(object obj, holdFrameID, int delay)` - Plays "take out weapon" animation for given **holdFrameID**. It is not required to have such weapon in critter's inventory. ----- -##### `void reg_anim_turn_towards(object, tile/target, delay)` -- Makes object change its direction to face given tile num or target object. +##### `void reg_anim_turn_towards(object obj, int/object tile/target, int delay)` +- Makes object change its direction to face given tile number or target object. ----- ##### `void reg_anim_callback(procedure proc)` @@ -261,18 +278,18 @@ FUNCTION REFERENCE ----- ##### `int/array metarule2_explosions(int arg1, int arg2, int arg3)` -- Was made as a dirty easy hack to allow dynamically change some explosion parameters (ranged attack). All changed parameters are reset to vanilla state automatically after each attack action. Following macros are available in **sfall.h**: +- Was made as a quick-and-dirty hack to enable dynamic changes to some explosion parameters for ranged attacks. All changed parameters are automatically reset to vanilla state after each attack action. Following macros are available in **sfall.h**: ----- -##### `void set_attack_explosion_pattern(x, y)` +##### `void set_attack_explosion_pattern(int x, int y)` - Currently `y` is not used and `x` means: 1 - reduced explosion pattern (3 effects are spawned instead of 7), 0 - full pattern. ----- -##### `void set_attack_explosion_art(x, y)` +##### `void set_attack_explosion_art(int x, int y)` - `y` is not used and `x` is a misc frame ID (last 3 bytes, without object type) to use for the next explosion. ----- -##### `void set_attack_explosion_radius(x)` +##### `void set_attack_explosion_radius(int x)` - Changes radius at which explosion will hit secondary targets for the next attack (from the experiments it is limited to something around 8 by the engine) ----- @@ -280,41 +297,41 @@ FUNCTION REFERENCE - If you call this right before using a weapon with fire damage type (e.g. in `HOOK_AFTERHITROLL`), it will produce explosion effects (and radius damage) just like "explosion" type, but all targets will still receive fire damage. ----- -##### `void set_explosion_radius(grenade, rocket)` +##### `void set_explosion_radius(int grenade, int rocket)` - Sets a permanent radius of the explosion for grenades and/or rockets. Passing 0 means not changing the corresponding radius. - Changed radius will be reset each time the player reloads the game. ----- -##### `array get_explosion_damage(itemPid)` +##### `array get_explosion_damage(int itemPid)` - Returns an array of the minimum and maximum damage of the explosive item. ----- -##### `void set_dynamite_damage(minDmg, maxDmg)` +##### `void set_dynamite_damage(int minDmg, int maxDmg)` - Sets the minimum and maximum damage for Dynamite. - Changed damage will be reset each time the player reloads the game. ----- -##### `void set_plastic_damage(minDmg, maxDmg)` +##### `void set_plastic_damage(int minDmg, int maxDmg)` - Sets the minimum and maximum damage for Plastic Explosives. - Changed damage will be reset each time the player reloads the game. ----- -##### `void set_explosion_max_targets(x)` +##### `void set_explosion_max_targets(int x)` - Sets the maximum number of additional targets for an explosion, valid range: 1..6 (default is 6) --- ### Some utility/math functions are available: -##### `array string_split(string text, split)` +##### `array string_split(string text, string split)` - Takes a string and a separator, searches the string for all instances of the separator, and returns a temp array filled with the pieces of the string split at each instance. If you give an empty string as the separator, the string is split into individual characters. - You can use this to search for a substring in a string like this: `strlen(get_array(string_split(haystack, needle), 0))` ----- -##### `string substr(string text, start, length)` +##### `string substr(string text, int start, int length)` - Cuts a substring from a string starting at `start` up to `length` characters. The first character position is 0 (zero). -- If `start` is negative - it indicates starting position from the end of the string (for example, `substr("test", -2, 2)` will return last 2 charactes: "st"). +- If `start` is negative - it indicates a position starting from the end of the string (for example, `substr("test", -2, 2)` will return last 2 charactes: "st"). - If `length` is negative - it means so many characters will be omitted from the end of string (example: `substr("test", 0, -2)` will return string without last 2 characters: "te"). -- If `length` is zero - it will return a string from the starting position to the end of the string (new behavior for sfall 4.2.2/3.8.22). +- If `length` is zero - it will return a string from the starting position to the end of the string (**new behavior** since sfall 4.2.2/3.8.22). ----- ##### `int strlen(string text)` @@ -322,7 +339,7 @@ FUNCTION REFERENCE ----- ##### `string sprintf(string format, any value)` -- Formats given value using standard syntax of C `printf` function (google "printf" for format details). However it is limited to formatting only 1 value. +- Formats given value using standard syntax of C `printf` function (google "printf" for format details). However, it is limited to formatting only 1 value. - Can be used to get character by ASCII code (`%c`). ----- @@ -403,8 +420,8 @@ FUNCTION REFERENCE ##### `string message_str_game(int fileId, int messageId)` - Works exactly the same as `message_str`, except you get messages from files in the `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` to `0x2FFF`. (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.) +- Additional game msg files added by **ExtraGameMsgFileList** setting will have consecutive fileIds assigned beginning from `0x2000` to `0x2FFF`. (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.) ----- ##### `int sneak_success()` @@ -477,7 +494,7 @@ sfall_funcX metarule functions ---- #### spatial_radius -`int sfall_func1("spatial_radius", object object)` +`int sfall_func1("spatial_radius", object obj)` - Returns radius of spatial script, associated with given dummy-object (returned by `create_spatial`) ---- @@ -523,22 +540,22 @@ sfall_funcX metarule functions ---- #### floor2 `int sfall_func1("floor2", int/float value)` -- Works just like vanilla `floor` function, but returns correct integers for negative values -- __NOTE:__ vanilla `floor` function works exactly the same as ceil for negative values, much like trunc in C/C++ +- Works just like vanilla `floor` function, but returns correct integers for negative floats +- __NOTE:__ vanilla `floor` function works exactly the same as `ceil` for negative floats (i.e. basically `trunc` in C/C++) ---- #### item_weight -`int sfall_func1("item_weight", object)` +`int sfall_func1("item_weight", object obj)` - Gets the current weight of an object ---- #### get_outline -`int sfall_func1("get_outline", object)` +`int sfall_func1("get_outline", object obj)` - Gets the current outline color for an object ---- #### set_outline -`void sfall_func2("set_outline", object, int color)` +`void sfall_func2("set_outline", object obj, int color)` - Sets the outline color of an object (see `OUTLINE_*` constants in **sfall.h**) - Can also set a custom color from the game palette by shifting the color index value left by 8 bits: `0xCC00` where `CC` is the palette index (**available since sfall 4.2.7/3.8.27**) - Passing 0 will disable the outline @@ -546,12 +563,12 @@ sfall_funcX metarule functions ---- #### get_flags -`int sfall_func1("get_flags", object)` +`int sfall_func1("get_flags", object obj)` - Gets the current value of object flags (see **define_extra.h** for available flags) ----- ##### set_flags -`void sfall_func2("set_flags", object, int flags)` +`void sfall_func2("set_flags", object obj, int flags)` - Sets the current flags of an object - All flags are rewritten with given integer, so first get current flags with `get_flags` and use `bwor`/`bwand` to set/remove specific flag @@ -567,9 +584,9 @@ sfall_funcX metarule functions ---- #### set_dude_obj -`object sfall_func1("set_dude_obj", critter)` +`object sfall_func1("set_dude_obj", int critter)` - Take control of a given critter -- passing 0 will reset control back to "real" dude +- Passing 0 will reset control back to "real" dude ---- #### real_dude_obj @@ -578,13 +595,15 @@ sfall_funcX metarule functions ---- #### get_ini_sections -`array sfall_func1("get_ini_sections", string fileName)` +`array sfall_func1("get_ini_sections", string file)` - Returns an array of names of all sections in a given INI file +- If the INI file is not found, it returns an empty array ---- #### get_ini_section -`array sfall_func2("get_ini_section", string fileName, string section)` +`array sfall_func2("get_ini_section", string file, string section)` - Returns an associative array of keys and values for a given INI file and section +- If the INI file is not found, it returns an empty array - __NOTE:__ all keys and their values will be of String type ---- @@ -619,12 +638,12 @@ sfall_funcX metarule functions ---- #### lock_is_jammed -`int sfall_func1("lock_is_jammed", object)` +`int sfall_func1("lock_is_jammed", object obj)` - Returns 1 if the lock (container or scenery) is currently jammed, 0 otherwise ---- #### unjam_lock -`void sfall_func1("unjam_lock", object)` +`void sfall_func1("unjam_lock", object obj)` - Unjams a lock immediately without having to wait until the next day, or leave the map and then return after 24 hours - __NOTE:__ does not work in `use_skill_on_p_proc` procedure @@ -670,8 +689,8 @@ sfall_funcX metarule functions ---- #### set_iface_tag_text `void sfall_func3("set_iface_tag_text", int tag, string text, int color)` -- Sets the text messages and colors for custom notification boxes to the interface without the need to add messages to intrface.msg and set up the font colors in ddraw.ini -- `tag` value is the same as used in `show_iface_tag`, `hide_iface_tag`, and `is_iface_tag_active`. The valid range is from 5 to (4 + the value of **BoxBarCount** in ddraw.ini) or the number of the last custom box added using the `add_ifaca_tag` function +- Sets the text messages and colors for custom notification boxes to the interface without the need to add messages to intrface.msg and set up the font colors in **ddraw.ini** +- `tag` value is the same as used in `show_iface_tag`, `hide_iface_tag`, and `is_iface_tag_active`. The valid range is from 5 to (4 + the value of **BoxBarCount** in **ddraw.ini**) or the number of the last custom box added using the `add_ifaca_tag` function - The text is limited to 19 characters - Available colors: 0 - green, 1 - red, 2 - white, 3 - yellow, 4 - dark yellow, 5 - blue, 6 - purple, 7 - dull pink @@ -702,7 +721,8 @@ sfall_funcX metarule functions ---- #### get_string_pointer `int sfall_func1("get_string_pointer", string text)` -- Returns a pointer to a string variable or to a text +- (DEPRECATED) Returns a pointer to a string variable or to a text +- __NOTE:__ this function is intended for use only in `HOOK_DESCRIPTIONOBJ`. Starting from sfall 4.4/3.8.40, you can return normal strings directly in the hook without calling the function ---- #### dialog_message @@ -711,7 +731,7 @@ sfall_funcX metarule functions ---- #### get_current_inven_size -`int sfall_func1("get_current_inven_size", object)` +`int sfall_func1("get_current_inven_size", object obj)` - Returns the current inventory size of the container or the critter ---- @@ -758,17 +778,17 @@ sfall_funcX metarule functions ---- #### get_object_data -`int sfall_func2("get_object_data", object, int offset)` +`int sfall_func2("get_object_data", object obj, int offset)` - Returns the data at the specified offset of an object (see `OBJ_DATA_*` constants in **define_extra.h** for offsets) ---- -#### set_object_data (object, offset, data) -`void sfall_func3("set_object_data", object, int offset, int data)` +#### set_object_data +`void sfall_func3("set_object_data", object obj, int offset, int data)` - Sets the data at the specified offset of an object ---- #### get_object_ai_data -`int sfall_func2("get_object_ai_data", object, int aiParam)` +`int sfall_func2("get_object_ai_data", object obj, int aiParam)` - Returns the setting value from the AI packet of an object (critter) - Use `AI_CAP_*` constants from **define_extra.h** for the aiParam argument to get AI value @@ -786,15 +806,15 @@ sfall_funcX metarule functions ---- #### set_drugs_data `void sfall_func3("set_drugs_data", int type, int pid, int value)` -- Overrides the parameters of drugs set in the configuration file (**DrugsFile** setting in ddraw.ini) +- Overrides the parameters of drugs set in the configuration file (**DrugsFile** setting in **ddraw.ini**) - `type`:\ 0 - changes the value of **NumEffects** for the drug (see **Drugs.ini** for the description of NumEffects)\ 1 - changes the duration of the addiction effect for the drug (a value of 1 = one game minute) ---- #### set_unique_id -`int sfall_func1("set_unique_id", object)`\ -`int sfall_func2("set_unique_id", object, int flag)` +`int sfall_func1("set_unique_id", object obj)`\ +`int sfall_func2("set_unique_id", object obj, int flag)` - Assigns a unique ID number to the object and returns it. If a unique ID number has already been assigned to an object, then ID number is returned without reassignment - Items with unique IDs will not stack with other items of the same type in the inventory - To just get the current ID number of an object, use `sfall_func2("get_object_data", object, OBJ_DATA_ID)` @@ -835,7 +855,7 @@ sfall_funcX metarule functions - __NOTE:__ if the msg file does not exist in the current language directory, the function will try to load it from the `text\English\game\` directory **Optional argument:** -- `fileNumber`: the file ID number for the `message_str_game` function. The available range is from `0x2000` to `0x2FFF` (see **ExtraGameMsgFileList** setting in ddraw.ini). Use **fileNumber** only if you want to add a message file without editing ddraw.ini or existing scripts to support the old way +- `fileNumber`: the file ID number for the `message_str_game` function. The available range is from `0x2000` to `0x2FFF` (see **ExtraGameMsgFileList** setting in **ddraw.ini**). Use **fileNumber** only if you want to add a message file without editing **ddraw.ini** or existing scripts to support the old way ---- #### unwield_slot @@ -910,9 +930,10 @@ sfall_funcX metarule functions ---- #### string_format -`string sfall_func3("string_format", string format, any val1, any val2, ...)` -- Formats given value using standard syntax of C `printf` function (google "printf" for format details). However it is limited to formatting up to 4 values -- Formatting is only supported for `%s` and `%d`, and the format string is limited to 1024 characters +`string sfall_func2("string_format", string format, any val1)`\ +`string sfall_funcX("string_format", string format, any val1, any val2, ...)` +- Formats given values using standard syntax of C `printf` function (google "printf" for format details). However, it is limited to formatting up to 7 values +- The format string is limited to 1024 characters ---- #### objects_in_radius @@ -1063,6 +1084,49 @@ sfall_funcX metarule functions `bool sfall_func1("obj_is_openable", object obj)` - Returns True if the object is openable (i.e. has an opening/closing animation), False otherwise +---- +##### set_spray_settings +`void sfall_func4("set_spray_settings", int centerMult, int centerDiv, int targetMult, int targetDiv)` + +- Allows changing the multipliers and divisors for the bullet distribution of burst attacks dynamically. All settings are automatically reset to default values (**ComputeSpray_\*** settings in **ddraw.ini**) after each attack action +- Should be called before the calculation of the bullet distribution (e.g. in `HOOK_TOHIT` or `HOOK_AMMOCOST`) +- `centerDiv/targetDiv`: the minimum value of divisor is 1 +- `centerMult/targetMult`: multiplier values are capped at divisor values +- __NOTE:__ refer to the description of **ComputeSpray_\*** settings in **ddraw.ini** for details of the bullet distribution of burst attacks + +---- +##### get_combat_free_move +`int sfall_func0("get_combat_free_move")` + +- Returns available "bonus move" points of the current critter's turn. For NPCs, this is always 0 unless changed by `set_combat_free_move` + +---- +##### set_combat_free_move +`void sfall_func1("set_combat_free_move", int value)` + +- Allows changing "bonus move" points (yellow lights on the interface bar) that can only be used for movement, not attacking +- Can be called from `HOOK_COMBATTURN` at the start of the turn (will not work on `dude_obj`) +- Can be called from `HOOK_STDPROCEDURE` with `combat_proc` event (will work on both NPCs and `dude_obj`) + +---- +#### get_ini_config +`array sfall_func2("get_ini_config", string file, bool searchDB)` +- Loads a given INI file and returns a permanent array (map) where keys are section names and values are permanent sub-arrays (maps) where keys and values are strings +- `searchDB`:\ + False - searches the file in the regular file system, like with all other ini-related functions\ + True - searches the file in database (DAT) files. If not found, then it will try the regular file system +- Subsequent calls for the same file will return the same array, unless it was disposed using `free_array` + +---- +#### string_find +`int sfall_func2("string_find", string haystack, string needle)`\ +`int sfall_func3("string_find", string haystack, string needle, int pos)` + +- Returns the position of the first occurrence of a `needle` string in a `haystack` string, or -1 if not found. The first character position is 0 (zero) + +**Optional argument:** +- `pos`: the position at which to start the search. If negative, it indicates a position starting from the end of the string + **** _See other documentation files (arrays.md, hookscripts.md) for related functions reference._ diff --git a/artifacts/scripting/sfall opcode list.md b/artifacts/scripting/sfall opcode list.md index a1c14a97a..2a0588072 100644 --- a/artifacts/scripting/sfall opcode list.md +++ b/artifacts/scripting/sfall opcode list.md @@ -310,7 +310,7 @@ _^ - These functions require AllowUnsafeScripting to be enabled in ddraw.ini_ 0x8251 - `int charcode(string string)`\ 0x8253 - `int typeof(any value)` -0x823a - `int get_tile_fid(int tile)` +0x823a - `int get_tile_fid(int tileData)` 0x823b - `int modified_ini()` diff --git a/artifacts/scripting/sfall opcode list.txt b/artifacts/scripting/sfall opcode list.txt index f0d6656a7..11e93ce5e 100644 --- a/artifacts/scripting/sfall opcode list.txt +++ b/artifacts/scripting/sfall opcode list.txt @@ -310,9 +310,9 @@ 0x8251 - int charcode(string string) 0x8253 - int typeof(any value) -0x823a - int get_tile_fid(int tile) +0x823a - int get_tile_fid(int tileData) -0x823b - int modified_ini +0x823b - int modified_ini() 0x823e - void force_aimed_shots(int pid) 0x823f - void disable_aimed_shots(int pid) diff --git a/artifacts/sfall.dat b/artifacts/sfall.dat index 116c0a989..241cfa536 100644 Binary files a/artifacts/sfall.dat and b/artifacts/sfall.dat differ diff --git a/artifacts/sfall_ru.dat b/artifacts/sfall_ru.dat index 2b8d73e0e..48a2cba1d 100644 Binary files a/artifacts/sfall_ru.dat and b/artifacts/sfall_ru.dat differ diff --git a/artifacts/sfall_zh.dat b/artifacts/sfall_zh.dat index 87e62ea8e..2c7397705 100644 Binary files a/artifacts/sfall_zh.dat and b/artifacts/sfall_zh.dat differ diff --git a/artifacts/translations/french.ini b/artifacts/translations/french.ini index 402c4209b..e57e8efb3 100644 --- a/artifacts/translations/french.ini +++ b/artifacts/translations/french.ini @@ -14,7 +14,7 @@ NPCPickupFail=%s ne peux pas prendre cet objet. AmmoInfoGlovz=Div. : RD/%d, SD/%d AmmoInfoYAAM=Mod. SD : %d -PartyOrderAttackHuman=Je m'en occupe|Okay, je fais ça|Bonne idée +PartyOrderAttackHuman=Je m'en occupe.|Okay, je fais ça.|Bonne idée. PartyOrderAttackCreature=::Grogne:: PartyOrderAttackRobot=::Biip:: diff --git a/docs/sslc.md b/docs/sslc.md index 32e2c1ed6..707e9e0fd 100644 --- a/docs/sslc.md +++ b/docs/sslc.md @@ -228,16 +228,6 @@ Syntax which requires sfall for compiled scripts to be interpreted is marked by end ``` -- Empty statements in blocks are allowed: This is just a convenience to save scripters a bit of memory. Some of the macros in the Fallout headers include their own semicolons while others do not. With the original compiler you had to remember which was which, and if you got it wrong the script would not compile. Now it's always safe to include your own semicolon, even if the macro already had its own. For example, this would not compile with the original sslc, but will with the sfall edition: - ``` - #define my_macro display_msg("foo"); - - procedure start begin - my_macro; - end - ``` - __NOTE:__ **Does not work currently.** - - Procedure stringify operator `@`: Designed to make callback-procedures a better option and allow for basic functional programming. Basically it replaces procedure names preceded by `@` by a string constant. - old: ``` diff --git a/sfall/CRC.cpp b/sfall/CRC.cpp index 0c3b5e974..3ceac33a8 100644 --- a/sfall/CRC.cpp +++ b/sfall/CRC.cpp @@ -84,7 +84,7 @@ static DWORD CalcCRCInternal(BYTE* data, DWORD size) { } static bool CheckExtraCRC(DWORD crc) { - auto extraCrcList = IniReader::GetListDefaultConfig("Debugging", "ExtraCRC", "", 512, ','); + auto extraCrcList = IniReader::GetListDefaultConfig("Debugging", "ExtraCRC", "", ','); if (!extraCrcList.empty()) { return std::any_of(extraCrcList.begin(), extraCrcList.end(), [crc](const std::string& testCrcStr) { auto testedCrc = strtoul(testCrcStr.c_str(), 0, 16); diff --git a/sfall/Config.cpp b/sfall/Config.cpp new file mode 100644 index 000000000..04e7860b6 --- /dev/null +++ b/sfall/Config.cpp @@ -0,0 +1,174 @@ +/* + * sfall + * Copyright (C) 2008-2023 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 "Config.h" + +#include "main.h" +#include "FalloutEngine\Fallout2.h" +#include "Utils.h" + +using namespace fo; + +namespace sfall +{ + +const Config::Data& Config::data() +{ + return _data; +} + +bool Config::read(const char* filePath, bool isDb) +{ + if (filePath == nullptr) { + return false; + } + + char string[2048]; + + if (isDb) { + DbFile* stream = func::db_fopen(filePath, "rb"); + + if (stream == nullptr) return false; + + while (func::db_fgets(string, sizeof(string), stream) != nullptr) { + parseLine(string); + } + func::db_fclose(stream); + } else { + FILE* stream = fopen(filePath, "rt"); + + // CE: Return false if file does not exist on the file system. + if (stream == nullptr) { + return false; + } + + while (fgets(string, sizeof(string), stream) != nullptr) { + parseLine(string); + } + fclose(stream); + } + return true; +} + +// Splits "key=value" pair from [string] and copy appropriate parts into [key] +// and [value] respectively. +// +// Both key and value are trimmed. +bool Config::parseKeyValue(char* string, std::string& key, std::string& value) +{ + if (string == nullptr) { + return false; + } + + // Find equals character. + char* pch = strchr(string, '='); + if (pch == nullptr) { + return false; + } + + *pch = '\0'; + trim(string); + key = string; + trim(pch + 1); + value = pch + 1; + return true; +} + +// TODO: +// bool Config::write(const char* filePath, bool isDb) {} + +// Based on original code with tweaks from Fallout CE. +bool Config::parseLine(char* string) +{ + char* pch; + + // Find comment marker and truncate the string. + pch = strchr(string, ';'); + if (pch != nullptr) { + *pch = '\0'; + } + + // Skip leading whitespace. + while (isspace(static_cast(*string))) { + string++; + } + + // Check if it's a section key. + if (*string == '[') { + char* sectionKey = string + 1; + + // Find closing bracket. + pch = strchr(sectionKey, ']'); + if (pch != nullptr) { + *pch = '\0'; + trim(sectionKey); + _lastSection = sectionKey; + return true; + } + } + + std::string key, value; + if (!parseKeyValue(string, key, value)) { + return false; + } + + ensureSection(_lastSection.c_str())->second.emplace(std::move(key), std::move(value)); + return true; +} + +Config::Data::iterator Config::ensureSection(const char* sectionKey) +{ + auto sectionIt = _data.find(sectionKey); + if (sectionIt == _data.end()) { + return _data.emplace(sectionKey, Section()).first; + } + return sectionIt; +} + +bool Config::getString(const char* sectionKey, const char* key, const std::string*& outValue) +{ + auto sectionIt = _data.find(sectionKey); + if (sectionIt == _data.end()) return false; + + const auto& section = sectionIt->second; + auto valueIt = section.find(key); + if (valueIt == section.end()) return false; + + outValue = &valueIt->second; + return true; +} + +bool Config::getInt(const char* sectionKey, const char* key, int& outValue, unsigned char base /* = 0 */) +{ + const std::string* value; + if (!getString(sectionKey, key, value)) return false; + + outValue = StrToLong(value->c_str(), base); + return true; +} + +bool Config::getDouble(const char* sectionKey, const char* key, double& outValue) +{ + const std::string* value; + if (!getString(sectionKey, key, value)) return false; + + outValue = strtod(value->c_str(), nullptr); + return true; +} + +} diff --git a/sfall/Config.h b/sfall/Config.h new file mode 100644 index 000000000..f7003dcbc --- /dev/null +++ b/sfall/Config.h @@ -0,0 +1,61 @@ +/* + * sfall + * Copyright (C) 2008-2023 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "Utils.h" + +#include +#include + +namespace sfall +{ + +/* + Implements reading from INI-like text config files in normal and DAT-filesystem. + Supposed to be 100% compatible with Fallout TXT config formats (such as worldmap.txt). +*/ +class Config { +public: + typedef std::map Section; + typedef std::map Data; + + bool read(const char* filePath, bool isDb); + + bool getString(const char* sectionKey, const char* key, const std::string*& outValue); + bool getInt(const char* sectionKey, const char* key, int& outValue, unsigned char base = 0); + bool getDouble(const char* sectionKey, const char* key, double& outValue); + + const Data& data(); + + // TODO: + // bool write(const char* filePath, bool isDb); + // bool setString(const char* sectionKey, const char* key, const char* value); + // bool setInt(const char* sectionKey, const char* key, int value); + // bool setDouble(const char* sectionKey, const char* key, double value); +private: + std::string _lastSection; + Data _data; + + static bool parseKeyValue(char* string, std::string& key, std::string& value); + + Data::iterator ensureSection(const char* sectionKey); + bool parseLine(char* string); +}; + +} diff --git a/sfall/ConsoleWindow.cpp b/sfall/ConsoleWindow.cpp new file mode 100644 index 000000000..5194a0cc5 --- /dev/null +++ b/sfall/ConsoleWindow.cpp @@ -0,0 +1,178 @@ +/* + * sfall + * Copyright (C) 2008-2023 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 "ConsoleWindow.h" + +#include "FalloutEngine\Fallout2.h" +#include "Modules\LoadGameHook.h" +#include "IniReader.h" +#include "Logging.h" +#include "SafeWrite.h" +#include "Utils.h" + +#include +#include + +namespace sfall +{ + +static constexpr char* IniSection = "Debugging"; +static constexpr char* IniModeKey = "ConsoleWindow"; +static constexpr char* IniPositionKey = "ConsoleWindowData"; +static constexpr char* IniCodePageKey = "ConsoleCodePage"; + +bool ConsoleWindow::tryGetWindow(HWND* wnd) { + *wnd = GetConsoleWindow(); + if (!*wnd) { + dlogr("Error getting console window.", DL_MAIN); + return false; + } + return true; +} + +BOOL WINAPI ConsoleCtrlHandler(DWORD dwCtrlType) { + if (dwCtrlType == CTRL_CLOSE_EVENT) { + ConsoleWindow::instance().savePosition(); + } + return TRUE; +} + +void ConsoleWindow::loadPosition() { + HWND wnd; + if (!tryGetWindow(&wnd)) return; + + if (HMENU hMenu = GetSystemMenu(wnd, FALSE)) { + EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | MF_DISABLED | MF_GRAYED); + } + if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE)) { + dlog_f("Error setting console ctrl handler: 0x%x\n", DL_MAIN, GetLastError()); + } + + auto windowDataStr = IniReader::GetStringDefaultConfig(IniSection, IniPositionKey, ""); + auto windowDataSplit = split(windowDataStr, ','); + + int windowData[5]; + for (size_t i = 0; i < 5; i++) { + windowData[i] = i < windowDataSplit.size() ? atoi(windowDataSplit.at(i).c_str()) : 0; + } + LONG w = max(windowData[2], 640), + h = max(windowData[3], 480), + x = windowData[0], + y = windowData[1]; + UINT showCmd = windowData[4] != 0 ? windowData[4] : SW_SHOWNORMAL; + + dlog_f("Setting console window position: (%d, %d), size: %dx%d, showCmd: %d\n", DL_MAIN, x, y, w, h, showCmd); + WINDOWPLACEMENT wPlacement{}; + wPlacement.length = sizeof(WINDOWPLACEMENT); + auto& rect = wPlacement.rcNormalPosition; + rect.left = x; + rect.top = y; + rect.right = x + w; + rect.bottom = y + h; + wPlacement.showCmd = showCmd; + if (!SetWindowPlacement(wnd, &wPlacement)) { + dlog_f("Error repositioning console window: 0x%x\n", DL_MAIN, GetLastError()); + } +} + +void ConsoleWindow::savePosition() { + HWND wnd; + if (!tryGetWindow(&wnd)) return; + + WINDOWPLACEMENT wPlacement; + wPlacement.length = sizeof(WINDOWPLACEMENT); + if (!GetWindowPlacement(wnd, &wPlacement)) { + dlog_f("Error getting console window placement: 0x%x\n", DL_MAIN, GetLastError()); + return; + } + RECT wndRect; + if (wPlacement.showCmd != SW_SHOWNORMAL) { + wndRect = wPlacement.rcNormalPosition; + } else if (!GetWindowRect(wnd, &wndRect)) { + dlog_f("Error getting console window rect: 0x%x\n", DL_MAIN, GetLastError()); + return; + } + int width = wndRect.right - wndRect.left; + int height = wndRect.bottom - wndRect.top; + std::ostringstream ss; + ss << wndRect.left << "," << wndRect.top << "," << width << "," << height << "," << wPlacement.showCmd; + auto wndDataStr = ss.str(); + dlog_f("Saving console window position & size: %s\n", DL_MAIN, wndDataStr.c_str()); + + IniReader::SetDefaultConfigString(IniSection, IniPositionKey, wndDataStr.c_str()); +} + +static void __fastcall WriteGameLog(const char* a) { + ConsoleWindow::instance().write(a, ConsoleWindow::Source::GAME); +} + +static void __declspec(naked) debug_printf_hook() { + __asm { + call fo::funcoffs::vsprintf_; + pushadc; + lea ecx, [esp + 16]; + call WriteGameLog; + popadc; + retn; + } +} + +void ConsoleWindow::init() { + _mode = IniReader::GetIntDefaultConfig(IniSection, IniModeKey, 0); + if (_mode == 0) return; + + if (!AllocConsole()) { + dlog_f("Failed to allocate console: 0x%x\n", DL_MAIN, GetLastError()); + return; + } + int cp = IniReader::GetIntDefaultConfig(IniSection, IniCodePageKey, 0); + if (cp > 0) SetConsoleOutputCP(cp); + + freopen("CONOUT$", "w", stdout); // this allows to print to console via std::cout + + if (_mode & Source::GAME) { + std::cout << "Displaying debug_printf output.\n"; + HookCall(0x4C6F77, debug_printf_hook); + } + if (_mode & Source::SFALL) { + std::cout << "Displaying sfall debug output.\n"; + } + if (_mode & Source::DEBUG_MSG) { + std::cout << "Displaying debug_msg output.\n"; + } + if (_mode & Source::DISPLAY_MSG) { + std::cout << "Displaying display_msg output.\n"; + } + std::cout << std::endl; + + loadPosition(); + + LoadGameHook::OnBeforeGameClose() += [this] { savePosition(); }; +} + +void ConsoleWindow::write(const char* message, ConsoleWindow::Source source) { + if (!(_mode & source)) return; + + if (source == Source::SFALL && _lastSource != Source::SFALL) { + std::cout << "\n"; // To make logs prettier, because debug_msg places newline before the message. + } + std::cout << message; + _lastSource = source; +} + +} diff --git a/sfall/ConsoleWindow.h b/sfall/ConsoleWindow.h new file mode 100644 index 000000000..ecaf30749 --- /dev/null +++ b/sfall/ConsoleWindow.h @@ -0,0 +1,55 @@ +/* + * sfall + * Copyright (C) 2008-2023 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +namespace sfall +{ + +class ConsoleWindow { +public: + + enum Source : int { + GAME = 1, + SFALL = 2, + DEBUG_MSG = 4, + DISPLAY_MSG = 8, + }; + + static ConsoleWindow& instance() { + static ConsoleWindow instance; + return instance; + } + + ConsoleWindow() {} + + void init(); + + void loadPosition(); + void savePosition(); + + void write(const char* message, Source source); + +private: + int _mode = 0; + Source _lastSource; + + bool tryGetWindow(HWND* wnd); +}; + +} diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index f05f1b1cd..6156f2604 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -206,9 +206,9 @@ fo::AttackSubType GetWeaponType(DWORD weaponFlag) { fo::AttackSubType::MELEE, fo::AttackSubType::MELEE, fo::AttackSubType::THROWING, - fo::AttackSubType::GUNS, - fo::AttackSubType::GUNS, - fo::AttackSubType::GUNS + fo::AttackSubType::RANGED, + fo::AttackSubType::RANGED, + fo::AttackSubType::RANGED }; DWORD type = weaponFlag & 0xF; return (type < 9) ? weapon_types[type] : fo::AttackSubType::NONE; diff --git a/sfall/FalloutEngine/Enums.h b/sfall/FalloutEngine/Enums.h index aea53f4c9..c981bc1fa 100644 --- a/sfall/FalloutEngine/Enums.h +++ b/sfall/FalloutEngine/Enums.h @@ -649,7 +649,7 @@ enum AttackSubType : long UNARMED = 1, MELEE = 2, THROWING = 3, - GUNS = 4 + RANGED = 4 }; enum BodyType : long diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 747557bf4..2945f0401 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -62,6 +62,7 @@ WRAP_WATCOM_FFUNC3(long, register_object_play_sfx, fo::GameObject*, object, cons WRAP_WATCOM_FFUNC3(long, scr_get_local_var, long, sid, long, varId, long*, value) WRAP_WATCOM_FFUNC3(long, scr_set_local_var, long, sid, long, varId, long, value) WRAP_WATCOM_FFUNC3(long, square_coord, long, square, long*, outX, long*, outY) +WRAP_WATCOM_FFUNC3(long, square_num, long, squareX, long, squaryY, long, elevation) // elevation is not used WRAP_WATCOM_FFUNC6(long, text_object_create, fo::GameObject*, object, const char*, text, long, font, long, colorText, long, colorOutline, fo::BoundRect*, rect) WRAP_WATCOM_FFUNC3(long, tile_coord, long, tile, long*, outX, long*, outY) // the fourth argument of the function is not used WRAP_WATCOM_FFUNC3(long, tile_num_in_direction, long, tile, long, rotation, long, distance) @@ -114,13 +115,14 @@ WRAP_WATCOM_FUNC1(long, folder_print_seperator, const char*, text) WRAP_WATCOM_FUNC1(void, freeColorBlendTable, long, color) WRAP_WATCOM_FUNC1(long, FMtext_width, const char*, text) WRAP_WATCOM_FUNC1(long, game_get_global_var, long, globalVar) +WRAP_WATCOM_FUNC1(void, gdialogDisplayMsg, const char*, message) WRAP_WATCOM_FUNC0(long, get_input) WRAP_WATCOM_FUNC1(fo::BlendColorTableData*, getColorBlendTable, long, color) // Searches for message ID in given message file and places result in result argument WRAP_WATCOM_FUNC3(const char*, getmsg, const fo::MessageList*, fileAddr, fo::MessageNode*, result, long, messageId) -WRAP_WATCOM_FUNC1(void, gdialogDisplayMsg, const char*, message) WRAP_WATCOM_FUNC1(void, gmouse_3d_set_mode, long, mode) WRAP_WATCOM_FUNC1(long, gmouse_set_cursor, long, picNum) +WRAP_WATCOM_FUNC0(void, grid_toggle) WRAP_WATCOM_FUNC1(long, gsound_background_volume_get_set, long, setVolume) WRAP_WATCOM_FUNC1(void, gsound_play_sfx_file, const char*, name) // Plays SFX sound with given name WRAP_WATCOM_FUNC1(fo::Window*, GNW_find, long, winRef) @@ -256,12 +258,13 @@ WRAP_WATCOM_FUNC2(long, skill_dec_point_force, fo::GameObject*, critter, long, s WRAP_WATCOM_FUNC2(long, skill_inc_point_force, fo::GameObject*, critter, long, skill) WRAP_WATCOM_FUNC1(long, skill_is_tagged, long, skill) WRAP_WATCOM_FUNC2(long, skill_level, fo::GameObject*, critter, long, statID) +WRAP_WATCOM_FUNC2(void, skill_get_tags, long*, tags, long, num) +WRAP_WATCOM_FUNC2(void, skill_set_tags, long*, tags, long, num) WRAP_WATCOM_FUNC2(long, stat_get_base, fo::GameObject*, critter, long, statID) WRAP_WATCOM_FUNC2(long, stat_get_base_direct, fo::GameObject*, critter, long, statID) WRAP_WATCOM_FUNC2(long, stat_get_bonus, fo::GameObject*, critter, long, statID) +WRAP_WATCOM_FUNC1(void, stat_recalc_derived, fo::GameObject*, critter) WRAP_WATCOM_FUNC3(long, stat_set_bonus, fo::GameObject*, critter, long, statID, long, amount) -WRAP_WATCOM_FUNC2(void, skill_get_tags, long*, tags, long, num) -WRAP_WATCOM_FUNC2(void, skill_set_tags, long*, tags, long, num) WRAP_WATCOM_FUNC2(long, stat_level, fo::GameObject*, critter, long, statId) WRAP_WATCOM_FUNC1(void, stat_pc_add_experience, long, amount) // Adds experience points to PC WRAP_WATCOM_FUNC1(long, text_font, long, fontNum) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index d2944d4af..d1c35e64f 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -603,9 +603,9 @@ struct CritInfo { long statMod; // Another bit field, using the same values as EffectFlags. If the stat check is failed, these are applied in addition to the earlier ones. long failureEffect; - // The message to show when this critical occurs, taken from combat.msg . + // The message to show when this critical occurs, taken from combat.msg. long message; - // Shown instead of Message if the stat check is failed. + // This is shown instead of Message if the stat check fails. long failMessage; }; long values[7]; diff --git a/sfall/FalloutEngine/Variables_def.h b/sfall/FalloutEngine/Variables_def.h index 2c8804bdd..9884299ae 100644 --- a/sfall/FalloutEngine/Variables_def.h +++ b/sfall/FalloutEngine/Variables_def.h @@ -216,7 +216,7 @@ VAR_(sneak_working, DWORD) // DWORD var VAR_(sound_music_path1, char*) VAR_(sound_music_path2, char*) VAR_(speech_volume, DWORD) -VAR_(square, DWORD) +VARA(square, DWORD*, 3) // use (square && 0xFFF) to get ground fid, and ((square >> 16) && 0xFFF) to get roof VAR_(square_rect, fo::SquareRect) // _square_y VAR_(squares, DWORD*) VARA(stack, DWORD, 10) diff --git a/sfall/Game/combatAI.cpp b/sfall/Game/combatAI.cpp index 62ab8de48..45ea8b526 100644 --- a/sfall/Game/combatAI.cpp +++ b/sfall/Game/combatAI.cpp @@ -19,13 +19,13 @@ namespace game namespace sf = sfall; static const long aiUseItemAPCost = 2; - +/* // Implementation of ai_can_use_weapon_ engine function with the HOOK_CANUSEWEAPON hook bool CombatAI::ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode) { bool result = fo::func::ai_can_use_weapon(source, weapon, hitMode); return sf::CanUseWeaponHook_Invoke(result, source, weapon, hitMode); } - +*/ static long drugUsePerfFixMode; void __stdcall CombatAI::ai_check_drugs(fo::GameObject* source) { diff --git a/sfall/Game/combatAI.h b/sfall/Game/combatAI.h index 5129ca0fd..5e4a62e2f 100644 --- a/sfall/Game/combatAI.h +++ b/sfall/Game/combatAI.h @@ -15,7 +15,7 @@ class CombatAI { public: static void init(); - static bool ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode); + //static bool ai_can_use_weapon(fo::GameObject* source, fo::GameObject* weapon, long hitMode); static void __stdcall ai_check_drugs(fo::GameObject* source); }; diff --git a/sfall/Game/items.cpp b/sfall/Game/items.cpp index e14016289..62eef3ed5 100644 --- a/sfall/Game/items.cpp +++ b/sfall/Game/items.cpp @@ -142,7 +142,7 @@ static long item_w_mp_cost_sub(fo::GameObject* source, fo::GameObject* item, lon if ((type == fo::AttackSubType::MELEE || type == fo::AttackSubType::UNARMED) && Stats::perk_level(source, fo::Perk::PERK_bonus_hth_attacks)) { cost--; } - if (type == fo::AttackSubType::GUNS && Stats::perk_level(source, fo::Perk::PERK_bonus_rate_of_fire)) { + if (type == fo::AttackSubType::RANGED && Stats::perk_level(source, fo::Perk::PERK_bonus_rate_of_fire)) { cost--; } if (cost < 1) cost = 1; diff --git a/sfall/HRP/Init.cpp b/sfall/HRP/Init.cpp index ca232651f..850f904c2 100644 --- a/sfall/HRP/Init.cpp +++ b/sfall/HRP/Init.cpp @@ -291,8 +291,8 @@ void Setting::init(const char* exeFileName, std::string &cmdline) { // add: patchXXX.dat > sfall.dat > [add here] > critter.dat > master.dat sf::LoadOrder::AddResourcePatches( - sf::IniReader::GetString("Main", "f2_res_dat", "f2_res.dat", MAX_PATH, f2ResIni), - sf::IniReader::GetString("Main", "f2_res_patches", "", MAX_PATH, f2ResIni) + sf::IniReader::GetString("Main", "f2_res_dat", "f2_res.dat", f2ResIni), + sf::IniReader::GetString("Main", "f2_res_patches", "", f2ResIni) ); /* Inject hacks */ diff --git a/sfall/IniReader.cpp b/sfall/IniReader.cpp index 6fc626733..b03490bc5 100644 --- a/sfall/IniReader.cpp +++ b/sfall/IniReader.cpp @@ -16,84 +16,136 @@ * along with this program. If not, see . */ -#include "Utils.h" - #include "IniReader.h" +#include "main.h" +#include "FalloutEngine\Fallout2.h" +#include "Modules\LoadGameHook.h" +#include "Config.h" +#include "Utils.h" + namespace sfall { -DWORD IniReader::modifiedIni; - static const char* ddrawIni = ".\\ddraw.ini"; -static char ini[65] = ".\\"; + +IniReader& IniReader::instance() { + static IniReader instance; + return instance; +} + +IniReader::IniReader() { +} + +Config* IniReader::getIniConfig(const char* iniFile) { + std::string pathStr(iniFile); + auto cacheHit = _iniCache.find(pathStr); + if (cacheHit != _iniCache.end()) { + return cacheHit->second.get(); + } + auto config = std::make_unique(); + if (!config->read(iniFile, false)) { + _iniCache.emplace(std::move(pathStr), nullptr); + return nullptr; + } + return _iniCache.emplace(std::move(pathStr), std::move(config)).first->second.get(); +} + +const char* IniReader::getConfigFile() { + return _ini; +} + +void IniReader::setDefaultConfigFile() { + std::strcpy(&_ini[2], &ddrawIni[2]); +} + +void IniReader::setConfigFile(const char* iniFile) { + strcat_s(_ini, iniFile); +} static int getInt(const char* section, const char* setting, int defaultValue, const char* iniFile) { - return GetPrivateProfileIntA(section, setting, defaultValue, iniFile); + auto config = IniReader::instance().getIniConfig(iniFile); + int value; + if (config == nullptr || !config->getInt(section, setting, value)) { + value = defaultValue; + } + return value; } static size_t getString(const char* section, const char* setting, const char* defaultValue, char* buf, size_t bufSize, const char* iniFile) { - return GetPrivateProfileStringA(section, setting, defaultValue, buf, bufSize, iniFile); + auto config = IniReader::instance().getIniConfig(iniFile); + const std::string* value; + const char* result = config != nullptr && config->getString(section, setting, value) + ? value->c_str() + : defaultValue; + + strncpy_s(buf, bufSize, result, bufSize - 1); + return strlen(buf); } -static std::string getString(const char* section, const char* setting, const char* defaultValue, size_t bufSize, const char* iniFile) { - char* buf = new char[bufSize]; - getString(section, setting, defaultValue, buf, bufSize, iniFile); - std::string str(buf); - delete[] buf; - return str; +static std::string getString(const char* section, const char* setting, const char* defaultValue, const char* iniFile) { + auto config = IniReader::instance().getIniConfig(iniFile); + const std::string* value; + if (config == nullptr || !config->getString(section, setting, value)) { + return std::string(defaultValue); + } + return *value; } -static std::vector getList(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter, const char* iniFile) { - auto list = split(getString(section, setting, defaultValue, bufSize, iniFile), delimiter); - std::transform(list.cbegin(), list.cend(), list.begin(), (std::string (*)(const std::string&))trim); - return list; +int IniReader::setString(const char* section, const char* setting, const char* value, const char* iniFile) { + _iniCache.erase(iniFile); // remove file from cache so it returns updated value on the next read + return WritePrivateProfileStringA(section, setting, value, iniFile); } -static int setInt(const char* section, const char* setting, int value, const char* iniFile) { - char buf[33]; - _itoa_s(value, buf, 33, 10); - return WritePrivateProfileStringA(section, setting, buf, iniFile); +void IniReader::clearCache() { + _iniCache.clear(); } -const char* IniReader::GetConfigFile() { - return ini; +void IniReader::init() { + _modifiedIni = IniReader::GetConfigInt("Main", "ModifiedIni", 0); + + LoadGameHook::OnGameReset() += [this] { clearCache(); }; } -void IniReader::SetDefaultConfigFile() { - std::strcpy(&ini[2], &ddrawIni[2]); + +static int setInt(const char* section, const char* setting, int value, const char* iniFile) { + char buf[33]; + _itoa_s(value, buf, 33, 10); + return IniReader::instance().setString(section, setting, buf, iniFile); } -void IniReader::SetConfigFile(const char* iniFile) { - strcat_s(ini, iniFile); +static std::vector getList(const char* section, const char* setting, const char* defaultValue, char delimiter, const char* iniFile) { + auto list = split(getString(section, setting, defaultValue, iniFile), delimiter); + std::transform(list.cbegin(), list.cend(), list.begin(), (std::string (*)(const std::string&))trim); + return list; } int IniReader::GetIntDefaultConfig(const char* section, const char* setting, int defaultValue) { return getInt(section, setting, defaultValue, ddrawIni); } -std::string IniReader::GetStringDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize) { - return getString(section, setting, defaultValue, bufSize, ddrawIni); +std::string IniReader::GetStringDefaultConfig(const char* section, const char* setting, const char* defaultValue) { + return getString(section, setting, defaultValue, ddrawIni); } -std::vector IniReader::GetListDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter) { - return getList(section, setting, defaultValue, bufSize, delimiter, ddrawIni); +std::vector IniReader::GetListDefaultConfig(const char* section, const char* setting, const char* defaultValue, char delimiter) { + return getList(section, setting, defaultValue, delimiter, ddrawIni); } int IniReader::GetConfigInt(const char* section, const char* setting, int defaultValue) { - return getInt(section, setting, defaultValue, ini); + return getInt(section, setting, defaultValue, instance()._ini); } -std::string IniReader::GetConfigString(const char* section, const char* setting, const char* defaultValue, size_t bufSize) { - return trim(getString(section, setting, defaultValue, bufSize, ini)); +std::string IniReader::GetConfigString(const char* section, const char* setting, const char* defaultValue) { + return trim(getString(section, setting, defaultValue, instance()._ini)); } size_t IniReader::GetConfigString(const char* section, const char* setting, const char* defaultValue, char* buf, size_t bufSize) { - return getString(section, setting, defaultValue, buf, bufSize, ini); + return getString(section, setting, defaultValue, buf, bufSize, instance()._ini); } -std::vector IniReader::GetConfigList(const char* section, const char* setting, const char* defaultValue, size_t bufSize) { - return getList(section, setting, defaultValue, bufSize, ',', ini); +std::vector IniReader::GetConfigList(const char* section, const char* setting, const char* defaultValue) { + return getList(section, setting, defaultValue, ',', instance()._ini); } int IniReader::GetInt(const char* section, const char* setting, int defaultValue, const char* iniFile) { @@ -104,16 +156,20 @@ size_t IniReader::GetString(const char* section, const char* setting, const char return getString(section, setting, defaultValue, buf, bufSize, iniFile); } -std::string IniReader::GetString(const char* section, const char* setting, const char* defaultValue, size_t bufSize, const char* iniFile) { - return getString(section, setting, defaultValue, bufSize, iniFile); +std::string IniReader::GetString(const char* section, const char* setting, const char* defaultValue, const char* iniFile) { + return getString(section, setting, defaultValue, iniFile); } -std::vector IniReader::GetList(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter, const char* iniFile) { - return getList(section, setting, defaultValue, bufSize, delimiter, iniFile); +std::vector IniReader::GetList(const char* section, const char* setting, const char* defaultValue, char delimiter, const char* iniFile) { + return getList(section, setting, defaultValue, delimiter, iniFile); } int IniReader::SetConfigInt(const char* section, const char* setting, int value) { - return setInt(section, setting, value, ini); + return setInt(section, setting, value, instance()._ini); +} + +int IniReader::SetConfigString(const char* section, const char* setting, const char* value) { + return instance().setString(section, setting, value, instance()._ini); } int IniReader::SetDefaultConfigInt(const char* section, const char* setting, int value) { @@ -121,11 +177,7 @@ int IniReader::SetDefaultConfigInt(const char* section, const char* setting, int } int IniReader::SetDefaultConfigString(const char* section, const char* setting, const char* value) { - return WritePrivateProfileStringA(section, setting, value, ddrawIni); -} - -void IniReader::init() { - modifiedIni = IniReader::GetConfigInt("Main", "ModifiedIni", 0); + return instance().setString(section, setting, value, ddrawIni); } } diff --git a/sfall/IniReader.h b/sfall/IniReader.h index 29ce12171..3b8610ef2 100644 --- a/sfall/IniReader.h +++ b/sfall/IniReader.h @@ -20,36 +20,31 @@ namespace sfall { +class Config; class IniReader { public: - static void init(); - - static DWORD modifiedIni; - - static const char* GetConfigFile(); - static void SetDefaultConfigFile(); - static void SetConfigFile(const char* iniFile); + static IniReader& instance(); // Gets the integer value from the default config (i.e. ddraw.ini) static int GetIntDefaultConfig(const char* section, const char* setting, int defaultValue); - static std::string GetStringDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize); + static std::string GetStringDefaultConfig(const char* section, const char* setting, const char* defaultValue); // Gets a list of values separated by the delimiter from the default config (i.e. ddraw.ini) - static std::vector GetListDefaultConfig(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter); + static std::vector GetListDefaultConfig(const char* section, const char* setting, const char* defaultValue, char delimiter); // Gets the integer value from sfall configuration INI file static int GetConfigInt(const char* section, const char* setting, int defaultValue); // Gets the string value from sfall configuration INI file with trim function - static std::string GetConfigString(const char* section, const char* setting, const char* defaultValue, size_t bufSize = 128); + static std::string GetConfigString(const char* section, const char* setting, const char* defaultValue); // Loads the string value from sfall configuration INI file into the provided buffer static size_t GetConfigString(const char* section, const char* setting, const char* defaultValue, char* buffer, size_t bufSize = 128); // Parses the comma-separated list from the settings from sfall configuration INI file - static std::vector GetConfigList(const char* section, const char* setting, const char* defaultValue, size_t bufSize = 128); + static std::vector GetConfigList(const char* section, const char* setting, const char* defaultValue); // Gets the integer value from given INI file static int GetInt(const char* section, const char* setting, int defaultValue, const char* iniFile); @@ -58,16 +53,44 @@ class IniReader { static size_t GetString(const char* section, const char* setting, const char* defaultValue, char* buf, size_t bufSize, const char* iniFile); // Gets the string value from given INI file - static std::string GetString(const char* section, const char* setting, const char* defaultValue, size_t bufSize, const char* iniFile); + static std::string GetString(const char* section, const char* setting, const char* defaultValue, const char* iniFile); // Parses the comma-separated list setting from given INI file - static std::vector GetList(const char* section, const char* setting, const char* defaultValue, size_t bufSize, char delimiter, const char* iniFile); + static std::vector GetList(const char* section, const char* setting, const char* defaultValue, char delimiter, const char* iniFile); static int SetConfigInt(const char* section, const char* setting, int value); + static int SetConfigString(const char* section, const char* setting, const char* value); + static int SetDefaultConfigInt(const char* section, const char* setting, int value); static int SetDefaultConfigString(const char* section, const char* setting, const char* value); + + void init(); + void clearCache(); + + DWORD modifiedIni() { return _modifiedIni; } + + const char* getConfigFile(); + void setDefaultConfigFile(); + void setConfigFile(const char* iniFile); + + // Gets a Config from an INI file at given path, relative to game root folder. + // Config is loaded once per given path when requested and only unloaded on game reset (returning to main menu). + Config* getIniConfig(const char* iniFile); + + // Sets the string value in a given INI file + int setString(const char* section, const char* setting, const char* value, const char* iniFile); + +private: + DWORD _modifiedIni; + char _ini[65]{ ".\\" }; + std::unordered_map> _iniCache; + + IniReader(); + + IniReader(IniReader const&) = delete; + void operator=(IniReader const&) = delete; }; } diff --git a/sfall/Logging.cpp b/sfall/Logging.cpp index a1e2a24f3..41ebc4058 100644 --- a/sfall/Logging.cpp +++ b/sfall/Logging.cpp @@ -16,10 +16,12 @@ * along with this program. If not, see . */ -#include "main.h" #include "Logging.h" -#ifndef NO_SFALL_DEBUG +#include "main.h" +#include "FalloutEngine\Fallout2.h" +#include "ConsoleWindow.h" +#include "Utils.h" #include @@ -29,39 +31,60 @@ namespace sfall static int DebugTypes = 0; static std::ofstream Log; +static int LastType = -1; +static int LastNewLine; + template -static void OutLog(T a) { - Log << a; +static void OutLog(T a, int type, bool newLine = false) { + std::ostringstream ss; + if (LastNewLine || type != LastType) { + ss << "[" << DebugTypeToStr(type) << "] "; + } + ss << a; + if (newLine) ss << "\n"; + std::string str = ss.str(); + + ConsoleWindow::instance().write(str.c_str(), ConsoleWindow::Source::SFALL); + + Log << str; Log.flush(); + + LastType = type; + LastNewLine = str.back() == '\n'; } -template -static void OutLogN(T a) { - Log << a << "\n"; - Log.flush(); +const char* DebugTypeToStr(int type) { + switch (type) { + case DL_INIT: return "Init"; + case DL_HOOK: return "Hook"; + case DL_SCRIPT: return "Script"; + case DL_CRITICALS: return "Crits"; + case DL_FIX: return "Fix"; + default: return "Main"; + } } void dlog(const char* a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLog(a); + OutLog(a, type); } } void dlog(const std::string& a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLog(a); + OutLog(a, type); } } void dlogr(const char* a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLogN(a); + OutLog(a, type, true); } } void dlogr(const std::string& a, int type) { if (type == DL_MAIN || (isDebug && (type & DebugTypes))) { - OutLogN(a); + OutLog(a, type, true); } } @@ -70,8 +93,10 @@ void dlog_f(const char* fmt, int type, ...) { va_list args; va_start(args, type); char buf[1024]; - vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); - OutLog(buf); + int written = vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); + if (written > 0) { + OutLog(buf, type); + } va_end(args); } } @@ -83,8 +108,10 @@ void devlog_f(const char* fmt, int type, ...) { va_list args; va_start(args, type); char buf[1024]; - vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); - OutLog(buf); + int written = vsnprintf_s(buf, sizeof(buf), _TRUNCATE, fmt, args); + if (written > 0) { + OutLog(buf, type); + } va_end(args); } } @@ -113,5 +140,3 @@ void LoggingInit() { } } - -#endif diff --git a/sfall/ModuleManager.cpp b/sfall/ModuleManager.cpp index 13ebfbdeb..10b89bb34 100644 --- a/sfall/ModuleManager.cpp +++ b/sfall/ModuleManager.cpp @@ -5,8 +5,6 @@ namespace sfall { -ModuleManager ModuleManager::_instance; - ModuleManager::ModuleManager() { } @@ -23,8 +21,4 @@ void ModuleManager::initAll() { } } -ModuleManager& ModuleManager::getInstance() { - return _instance; -} - } diff --git a/sfall/ModuleManager.h b/sfall/ModuleManager.h index c1a5eb07d..6ec2e1aea 100644 --- a/sfall/ModuleManager.h +++ b/sfall/ModuleManager.h @@ -23,16 +23,17 @@ class ModuleManager { _modules.emplace_back(new T()); } - static ModuleManager& getInstance(); + static ModuleManager& instance() { + static ModuleManager instance; + return instance; + } private: // disallow copy constructor and copy assignment because we're dealing with unique_ptr's here - ModuleManager(const ModuleManager&); - void operator = (const ModuleManager&) {} + ModuleManager(const ModuleManager&) = delete; + void operator = (const ModuleManager&) = delete; std::vector> _modules; - - static ModuleManager _instance; }; } diff --git a/sfall/Modules/Animations.h b/sfall/Modules/Animations.h index 65f8efb36..a57766fbe 100644 --- a/sfall/Modules/Animations.h +++ b/sfall/Modules/Animations.h @@ -27,7 +27,6 @@ class Animations : public Module { public: const char* name() { return "Animations"; } void init(); - //void exit() override; }; } diff --git a/sfall/Modules/BarBoxes.cpp b/sfall/Modules/BarBoxes.cpp index 087340864..1cc50ffef 100644 --- a/sfall/Modules/BarBoxes.cpp +++ b/sfall/Modules/BarBoxes.cpp @@ -276,8 +276,8 @@ void BarBoxes::init() { boxes[i].msg = 100 + i; } - auto boxBarColors = IniReader::GetConfigString("Misc", "BoxBarColours", "", actualBoxCount + 1); - for (size_t i = 0; i < boxBarColors.size(); i++) { + auto boxBarColors = IniReader::GetConfigString("Misc", "BoxBarColours", ""); + for (size_t i = 0; i < boxBarColors.size() && i < (size_t)actualBoxCount; i++) { if (boxBarColors[i] == '1') { boxes[i + 5].colour = 1; // red color boxText[i].cfgColor = 1; diff --git a/sfall/Modules/Books.cpp b/sfall/Modules/Books.cpp index ac0e5f634..2a20dcd54 100644 --- a/sfall/Modules/Books.cpp +++ b/sfall/Modules/Books.cpp @@ -86,11 +86,12 @@ static void LoadVanillaBooks() { } void Books::init() { - auto booksFile = IniReader::GetConfigString("Misc", "BooksFile", "", MAX_PATH); + auto booksFile = IniReader::GetConfigString("Misc", "BooksFile", ""); if (!booksFile.empty()) { - dlog("Applying books patch...", DL_INIT); const char* iniBooks = booksFile.insert(0, ".\\").c_str(); + if (GetFileAttributesA(iniBooks) == INVALID_FILE_ATTRIBUTES) return; + dlog("Applying books patch...", DL_INIT); bool includeVanilla = (IniReader::GetInt("main", "overrideVanilla", 0, iniBooks) == 0); if (includeVanilla) BooksCount = 5; diff --git a/sfall/Modules/BugFixes.cpp b/sfall/Modules/BugFixes.cpp index 6603c7c41..8fb532c9d 100644 --- a/sfall/Modules/BugFixes.cpp +++ b/sfall/Modules/BugFixes.cpp @@ -2299,7 +2299,7 @@ static void __declspec(naked) ai_search_inven_weap_hook0() { mov edx, [esi + ammoPid]; test edx, edx; js skip; - mov eax, GUNS; // set GUNS if has ammo pid + mov eax, RANGED; // set RANGED if has ammo pid skip: retn; } @@ -3274,13 +3274,11 @@ void BugFixes::init() { MakeCall(0x46AB68, NegateFixHack); // Fix incorrect int-to-float conversion - // replace operator to "fild 32bit" - // op_mult: - SafeWrite16(0x46A3F4, 0x04DB); - SafeWrite16(0x46A3A8, 0x04DB); - // op_div: - SafeWrite16(0x46A566, 0x04DB); - SafeWrite16(0x46A4E7, 0x04DB); + SafeWriteBatch(0x04DB, { // fild 64bit > fild 32bit + 0x46A3A8, 0x46A3F4, // op_mul_ + 0x46A4E7, 0x46A566, // op_div_ + 0x46A280, 0x46A2CD, // op_sub_ + }); // Fix for vanilla division operator treating negative integers as unsigned //if (IniReader::GetConfigInt("Misc", "DivisionOperatorFix", 1)) { diff --git a/sfall/Modules/BurstMods.cpp b/sfall/Modules/BurstMods.cpp index 0f70466dd..65037a630 100644 --- a/sfall/Modules/BurstMods.cpp +++ b/sfall/Modules/BurstMods.cpp @@ -17,8 +17,10 @@ */ #include "..\main.h" +#include "..\FalloutEngine\Fallout2.h" #include "BurstMods.h" +#include "MainLoopHook.h" namespace sfall { @@ -28,6 +30,14 @@ static long compute_spray_center_div; static long compute_spray_target_mult; static long compute_spray_target_div; +// default values +static long compute_spray_center_mult_def; +static long compute_spray_center_div_def; +static long compute_spray_target_mult_def; +static long compute_spray_target_div_def; + +static bool computeSpraySettingsReset = false; + static long __fastcall ComputeSpray(DWORD* roundsLeftOut, DWORD* roundsRightOut, DWORD totalRounds, DWORD* roundsCenterOut) { // roundsCenter = totalRounds * mult / div long result = totalRounds * compute_spray_center_mult; @@ -37,9 +47,14 @@ static long __fastcall ComputeSpray(DWORD* roundsLeftOut, DWORD* roundsRightOut, if (roundsCenter == 0) roundsCenter = 1; *roundsCenterOut = roundsCenter; - long roundsLeft = (totalRounds - roundsCenter) / 2; + long roundsLeft = (totalRounds - roundsCenter) / 2; // minimum possible value is 0 + long roundsRight = totalRounds - roundsCenter - roundsLeft; // is either equal to or one more than roundsLeft + if (roundsLeft != roundsRight && fo::func::roll_random(0, 1)) { // randomize the distribution of one extra bullet + roundsLeft++; + roundsRight--; + } *roundsLeftOut = roundsLeft; - *roundsRightOut = totalRounds - roundsCenter - roundsLeft; + *roundsRightOut = roundsRight; // roundsMainTarget = roundsCenter * mult / div result = roundsCenter * compute_spray_target_mult; @@ -65,26 +80,55 @@ static void __declspec(naked) compute_spray_rounds_distribution() { } } +void BurstMods::SetComputeSpraySettings(long centerMult, long centerDiv, long targetMult, long targetDiv) { + compute_spray_center_mult = centerMult; + compute_spray_center_div = centerDiv; + compute_spray_target_mult = targetMult; + compute_spray_target_div = targetDiv; + computeSpraySettingsReset = true; +} + +void ResetComputeSpraySettings() { + if (!computeSpraySettingsReset) return; + compute_spray_center_mult = compute_spray_center_mult_def; + compute_spray_center_div = compute_spray_center_div_def; + compute_spray_target_mult = compute_spray_target_mult_def; + compute_spray_target_div = compute_spray_target_div_def; + computeSpraySettingsReset = false; +} + void BurstMods::init() { - //if (IniReader::GetConfigInt("Misc", "ComputeSprayMod", 0)) { + //if (IniReader::GetConfigInt("Misc", "ComputeSprayMod", 1)) { dlogr("Applying ComputeSpray settings to burst attacks.", DL_INIT); compute_spray_center_mult = IniReader::GetConfigInt("Misc", "ComputeSpray_CenterMult", 1); compute_spray_center_div = IniReader::GetConfigInt("Misc", "ComputeSpray_CenterDiv", 3); if (compute_spray_center_div < 1) { compute_spray_center_div = 1; } - if (compute_spray_center_mult > compute_spray_center_div) { + if (compute_spray_center_mult < 1) { + compute_spray_center_mult = 1; + } else if (compute_spray_center_mult > compute_spray_center_div) { compute_spray_center_mult = compute_spray_center_div; } + compute_spray_center_mult_def = compute_spray_center_mult; + compute_spray_center_div_def = compute_spray_center_div; + compute_spray_target_mult = IniReader::GetConfigInt("Misc", "ComputeSpray_TargetMult", 1); compute_spray_target_div = IniReader::GetConfigInt("Misc", "ComputeSpray_TargetDiv", 2); if (compute_spray_target_div < 1) { compute_spray_target_div = 1; } - if (compute_spray_target_mult > compute_spray_target_div) { + if (compute_spray_target_mult < 1) { + compute_spray_target_mult = 1; + } else if (compute_spray_target_mult > compute_spray_target_div) { compute_spray_target_mult = compute_spray_target_div; } + compute_spray_target_mult_def = compute_spray_target_mult; + compute_spray_target_div_def = compute_spray_target_div; + MakeJump(0x4234F1, compute_spray_rounds_distribution); + // after each combat attack, reset ComputeSpray settings + MainLoopHook::OnAfterCombatAttack() += ResetComputeSpraySettings; //} } diff --git a/sfall/Modules/BurstMods.h b/sfall/Modules/BurstMods.h index 1b2fad7dd..948e3772b 100644 --- a/sfall/Modules/BurstMods.h +++ b/sfall/Modules/BurstMods.h @@ -27,6 +27,8 @@ class BurstMods : public Module { public: const char* name() { return "BurstMods"; } void init(); + + static void SetComputeSpraySettings(long centerMult, long centerDiv, long targetMult, long targetDiv); }; } diff --git a/sfall/Modules/Combat.cpp b/sfall/Modules/Combat.cpp index 84035e526..ed13e4fe2 100644 --- a/sfall/Modules/Combat.cpp +++ b/sfall/Modules/Combat.cpp @@ -87,12 +87,12 @@ static bool hookedAimedShot; static std::vector disabledAS; static std::vector forcedAS; -static bool checkWeaponAmmoCost; +//static bool checkWeaponAmmoCost; // Compares the cost (required count of rounds) for one shot with the current amount of ammo to make an attack or other checks -long __fastcall Combat::check_item_ammo_cost(fo::GameObject* weapon, fo::AttackType hitMode) { +static long __fastcall check_item_ammo_cost(fo::GameObject* weapon, fo::AttackType hitMode) { long currAmmo = game::Items::item_w_curr_ammo(weapon); - if (!checkWeaponAmmoCost || currAmmo <= 0) return currAmmo; + if (currAmmo <= 0) return 0; long rounds = 1; // default ammo for single shot @@ -110,7 +110,7 @@ long __fastcall Combat::check_item_ammo_cost(fo::GameObject* weapon, fo::AttackT } // calculate the cost - long cost = (newRounds != rounds) ? newRounds / rounds : 1; // 1 - default cost + long cost = (newRounds != rounds) ? (newRounds / rounds) : 1; // 1 - default cost return (cost > currAmmo) ? 0 : currAmmo; // 0 - this will force "Not Enough Ammo" } @@ -120,7 +120,7 @@ static void __declspec(naked) combat_check_bad_shot_hook() { push edx; push ecx; // weapon mov edx, edi; // hitMode - call Combat::check_item_ammo_cost; + call check_item_ammo_cost; pop ecx; pop edx; retn; @@ -134,7 +134,7 @@ static void __declspec(naked) ai_search_inven_weap_hook() { push ecx; mov edx, ATKTYPE_RWEAPON_PRIMARY; // hitMode mov ecx, eax; // weapon - call Combat::check_item_ammo_cost; // enough ammo? + call check_item_ammo_cost; // enough ammo? pop ecx; retn; } @@ -171,11 +171,12 @@ static long __fastcall divide_burst_rounds_by_ammo_cost(long currAmmo, fo::GameO roundsCost = burstRounds; // rounds in burst (the number of rounds fired in the burst) AmmoCostHook_Script(2, weapon, roundsCost); // roundsCost returns the new cost } + if (roundsCost == 0) return burstRounds; // cost is free, skip the rest of calculation long cost = burstRounds * roundsCost; // amount of ammo required for this burst (multiplied by 1 or by the value returned from HOOK_AMMOCOST) if (cost > currAmmo) cost = currAmmo; // if cost ammo more than current ammo, set it to current - return (cost / roundsCost); // divide back to get proper number of rounds for damage calculations + return max(1, (cost / roundsCost)); // divide back to get proper number of rounds for damage calculations (minimum is 1) } static void __declspec(naked) compute_spray_hack() { @@ -263,11 +264,20 @@ static void __declspec(naked) determine_to_hit_func_hack() { mov edx, edi; // critter mov ecx, esi; // base (calculated hit chance) call HitChanceMod; - mov esi, eax; + mov esi, 999; // max + cmp eax, esi; + cmovl esi, eax; retn; } } +static void __declspec(naked) determine_to_hit_func_hook_min() { + __asm { + mov esi, -99; // min + jmp fo::funcoffs::debug_printf_; + } +} + bool Combat::IsBurstDisabled(fo::GameObject* critter) { for (size_t i = 0; i < noBursts.size(); i++) { if (noBursts[i] == critter->id) return true; @@ -354,7 +364,7 @@ void __stdcall KnockbackRemoveMod(fo::GameObject* object, DWORD mode) { } } -void __stdcall SetHitChanceMax(fo::GameObject* critter, DWORD maximum, DWORD mod) { +void __stdcall SetHitChanceMax(fo::GameObject* critter, int maximum, int mod) { if ((DWORD)critter == -1) { baseHitChance.maximum = maximum; baseHitChance.mod = mod; @@ -568,12 +578,15 @@ void Combat::init() { MakeCall(0x424791, determine_to_hit_func_hack); // HitChanceMod BlockCall(0x424796); + // Add a lower limit of -99% to the calculated hit chance instead of only a debug message + SafeWrite8(0x42479D, -99); // was -100 + HookCall(0x4247A5, determine_to_hit_func_hook_min); + // Disables secondary burst attacks for the critter MakeCall(0x429E44, ai_pick_hit_mode_hack_noBurst, 1); - checkWeaponAmmoCost = (IniReader::GetConfigInt("Misc", "CheckWeaponAmmoCost", 0) != 0); - if (checkWeaponAmmoCost) { - MakeCall(0x4234B3, compute_spray_hack, 1); + MakeCall(0x4234B3, compute_spray_hack, 1); // proper calculation of the number of burst rounds + if (IniReader::GetConfigInt("Misc", "CheckWeaponAmmoCost", 1)) { HookCall(0x4266E9, combat_check_bad_shot_hook); HookCall(0x429A37, ai_search_inven_weap_hook); // check if there is enough ammo to shoot HookCall(0x42A95D, ai_try_attack_hook); // jz func diff --git a/sfall/Modules/Combat.h b/sfall/Modules/Combat.h index 2581ecc42..319a8d57b 100644 --- a/sfall/Modules/Combat.h +++ b/sfall/Modules/Combat.h @@ -49,12 +49,10 @@ class Combat : public Module { static long determineHitChance; - static long __fastcall check_item_ammo_cost(fo::GameObject* weapon, fo::AttackType hitMode); - static bool IsBurstDisabled(fo::GameObject* critter); }; -void __stdcall SetHitChanceMax(fo::GameObject* critter, DWORD maximum, DWORD mod); +void __stdcall SetHitChanceMax(fo::GameObject* critter, int maximum, int mod); void __stdcall KnockbackSetMod(fo::GameObject* object, DWORD type, float val, DWORD mode); void __stdcall KnockbackRemoveMod(fo::GameObject* object, DWORD mode); diff --git a/sfall/Modules/Console.cpp b/sfall/Modules/Console.cpp index 2eba2961c..10971b289 100644 --- a/sfall/Modules/Console.cpp +++ b/sfall/Modules/Console.cpp @@ -61,7 +61,7 @@ void Console::PrintFile(const char* msg) { } void Console::init() { - auto path = IniReader::GetConfigString("Misc", "ConsoleOutputPath", "", MAX_PATH); + auto path = IniReader::GetConfigString("Misc", "ConsoleOutputPath", ""); if (!path.empty()) { consoleFile.open(path); if (consoleFile.is_open()) { diff --git a/sfall/Modules/Criticals.cpp b/sfall/Modules/Criticals.cpp index 447440469..1a42d452a 100644 --- a/sfall/Modules/Criticals.cpp +++ b/sfall/Modules/Criticals.cpp @@ -79,7 +79,7 @@ void Criticals::ResetCriticalTable(DWORD critter, DWORD bodypart, DWORD slot, DW static int CritTableLoad() { if (mode == 1) { dlogr("Setting up critical hit table using CriticalOverrides.ini (old fmt).", DL_CRITICALS); - if (GetFileAttributes(critTableFile.c_str()) == INVALID_FILE_ATTRIBUTES) return 1; + if (GetFileAttributesA(critTableFile.c_str()) == INVALID_FILE_ATTRIBUTES) return 1; char section[16]; for (DWORD critter = 0; critter < 20; critter++) { for (DWORD part = 0; part < 9; part++) { @@ -109,7 +109,7 @@ static int CritTableLoad() { if (mode == 3) { dlogr(" and CriticalOverrides.ini (new fmt).", DL_CRITICALS); - if (GetFileAttributes(critTableFile.c_str()) == INVALID_FILE_ATTRIBUTES) return 1; + if (GetFileAttributesA(critTableFile.c_str()) == INVALID_FILE_ATTRIBUTES) return 1; char buf[32], buf2[32], buf3[32]; for (int critter = 0; critter < Criticals::critTableCount; critter++) { sprintf_s(buf, "c_%02d", critter); @@ -315,7 +315,7 @@ void Criticals::init() { mode = IniReader::GetConfigInt("Misc", "OverrideCriticalTable", 2); if (mode < 0 || mode > 3) mode = 0; if (mode) { - critTableFile += IniReader::GetConfigString("Misc", "OverrideCriticalFile", "CriticalOverrides.ini", MAX_PATH); + critTableFile += IniReader::GetConfigString("Misc", "OverrideCriticalFile", "CriticalOverrides.ini"); CriticalTableOverride(); LoadGameHook::OnBeforeGameStart() += []() { memcpy(critTable, baseCritTable, sizeof(critTable)); // Apply loaded critical table diff --git a/sfall/Modules/CritterStats.cpp b/sfall/Modules/CritterStats.cpp index 3964572fe..e7be82f6c 100644 --- a/sfall/Modules/CritterStats.cpp +++ b/sfall/Modules/CritterStats.cpp @@ -158,14 +158,14 @@ static void SetStatValue(long* proto, long offset, long amount) { static void ModifyAllStats(const itProtoMem &mem) { if (!mem->second.proto && !fo::util::CritterCopyProto(mem->second.pid, mem->second.proto)) return; // proto error - for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { + for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); ++itBonus) { if (itBonus->objID == mem->first && itBonus->objPID == mem->second.pid) { itBonus->s_proto = mem->second.proto; SetBonusStatValue(itBonus->s_proto, itBonus->stat, itBonus->amount); mem->second.sharedCount++; } } - for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { + for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); ++itBase) { if (itBase->objID == mem->first && itBase->objPID == mem->second.pid) { itBase->s_proto = mem->second.proto; SetBaseStatValue(itBase->s_proto, itBase->stat, itBase->amount); @@ -189,7 +189,7 @@ static void AddStat(long stat, fo::GameObject* critter, long amount, long* defau fo::func::dev_printf("[SFALL] Set bonus:%d stat:%d, to NPC pid: %d, saved:%d\n", (offset == OffsetStat::bonus), stat, (critter->protoId & 0xFFFF), isSaved); offset += stat; - for (auto itStat = vec.begin(); itStat != vec.end(); itStat++) { + for (auto itStat = vec.begin(); itStat != vec.end(); ++itStat) { if (itStat->objID == critter->id && itStat->objPID == critter->protoId && itStat->stat == stat) { fo::func::dev_printf("[SFALL] Modify stat value old: %d to new: %d, ID: %d\n", itStat->s_proto[offset], amount, itStat->objID); if (amount == itStat->defVal) { // set value and value in regular prototype are matched @@ -222,9 +222,9 @@ static void __fastcall SetStatToProto(long stat, fo::GameObject* critter, long a } static void UpdateDefValue(std::vector &vec, long stat, long pid, long amount) { - for (auto itStat = vec.begin(); itStat != vec.end(); itStat++) { + for (auto itStat = vec.begin(); itStat != vec.end(); ++itStat) { if (itStat->objPID == pid && itStat->stat == stat) { - fo::func::dev_printf("[SFALL] Replace stat default value: %d to: %d, NPC ID: %d\n", itStat->defVal, amount, itStat->objID); + fo::func::dev_printf("[SFALL] Update stat default value: %d to: %d, NPC ID: %d\n", itStat->defVal, amount, itStat->objID); itStat->defVal = amount; } } @@ -374,8 +374,9 @@ long CritterStats::GetStat(fo::GameObject* critter, long stat, long offset) { // the changed value will not be saved when saving the game and will be reset when the player leaves the location map void CritterStats::SetStat(fo::GameObject* critter, long stat, long amount, long offset) { long* proto; - if (fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto) != -1) { + if (fo::util::GetProto(critter->protoId, (fo::Proto**)&proto)) { SetStatToProto(stat, critter, amount, proto, offset, false); // non-saveable stat + if (stat <= fo::STAT_lu) fo::func::stat_recalc_derived(critter); // Prerequisite: stat >= 0 } } @@ -386,10 +387,10 @@ static void ClearAllStats() { } static void FlushAllProtos() { - for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); itBonus++) { + for (auto itBonus = s_bonusStatProto.begin(); itBonus != s_bonusStatProto.end(); ++itBonus) { itBonus->s_proto = nullptr; } - for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); itBase++) { + for (auto itBase = s_baseStatProto.begin(); itBase != s_baseStatProto.end(); ++itBase) { itBase->s_proto = nullptr; } for (auto& mem : protoMem) { diff --git a/sfall/Modules/DamageMod.cpp b/sfall/Modules/DamageMod.cpp index 2c4a10c08..ae7942ba5 100644 --- a/sfall/Modules/DamageMod.cpp +++ b/sfall/Modules/DamageMod.cpp @@ -36,7 +36,8 @@ int DamageMod::formula; static char ammoInfoFmt[32]; // Integer division w/ round half to even for Glovz's damage formula -// Prerequisite: both dividend and divisor must be positive integers (should already be handled in the main function) +// Prerequisite: divisor must be a positive integer (should already be handled in the main function) +// if dividend is negative, the result will be 0 or 1 (for values in range of -1073741825 to -2147483646) static long DivRound(long dividend, long divisor) { if (dividend <= divisor) { // if equal then return 1 diff --git a/sfall/Modules/DebugEditor.cpp b/sfall/Modules/DebugEditor.cpp index 0583e5434..474907390 100644 --- a/sfall/Modules/DebugEditor.cpp +++ b/sfall/Modules/DebugEditor.cpp @@ -19,6 +19,7 @@ #include #include "..\main.h" +#include "..\ConsoleWindow.h" #include "..\FalloutEngine\Fallout2.h" #include "..\InputFuncs.h" //#include "Graphics.h" @@ -320,19 +321,8 @@ static void __declspec(naked) art_data_size_hook() { push edx; push artDbgMsg; call fo::funcoffs::debug_printf_; - cmp isDebug, 0; - jne display; add esp, 8; retn; -display: - push edx; // filename - push artDbgMsg; - lea eax, [esp + 0x124 - 0x124 + 20]; // buf - push eax; - call fo::funcoffs::sprintf_; - add esp, 20; - lea eax, [esp + 4]; - jmp fo::funcoffs::display_print_; } } @@ -405,6 +395,45 @@ static void __declspec(naked) combat_load_hack() { } } +static void __fastcall DuplicateLogToConsole(const char* a, unsigned long displayMsg) { + auto& console = ConsoleWindow::instance(); + auto source = displayMsg ? ConsoleWindow::Source::DISPLAY_MSG : ConsoleWindow::Source::DEBUG_MSG; + console.write("\n", source); + console.write(a, source); +} + +static void __declspec(naked) op_display_debug_msg_hack() { + __asm { + mov eax, 0x505224; // "\n" + call ds:[FO_VAR_debug_func]; + mov eax, esi; // actual message + call ds:[FO_VAR_debug_func]; + pushadc; + mov ecx, esi; + mov edx, [esp + 12]; + call DuplicateLogToConsole; // duplicate messages to console window + popadc; + add esp, 4; // eat displayMsg flag + pop eax; + add eax, 17; // skip to the end of functions + jmp eax; + } +} + +static void __declspec(naked) op_display_msg_hack() { + __asm { + push 1; // displayMsg = true + jmp op_display_debug_msg_hack; + } +} + +static void __declspec(naked) op_debug_msg_hack() { + __asm { + push 0; // displayMsg = false + jmp op_display_debug_msg_hack; + } +} + // Shifts the string one character to the right and inserts a newline control character at the beginning static void MoveDebugString(char* messageAddr) { int i = 0; @@ -451,20 +480,14 @@ static void DebugModePatch() { MakeJump(0x453FD2, dbg_error_hack); } - // prints a debug message about a missing critter art file to both debug.log and the message window in sfall debugging mode - HookCall(0x419B65, art_data_size_hook); - // checks the animation code, if ANIM_walk then skip printing the debug message - HookCall(0x419AA0, art_data_size_hook_check); - SafeWrite8(0x419B61, CodeType::JumpNZ); // jz > jnz - // Fix to prevent crashes when there is a '%' character in the printed message if (dbgMode > 1) { MakeCall(0x4C703F, debug_log_hack); BlockCall(0x4C7044); // just nop code } - // replace calling debug_printf_ with _debug_func - __int64 data = 0x51DF0415FFF08990; // mov eax, esi; call ds:_debug_func - SafeWriteBytes(0x455419, (BYTE*)&data, 8); // op_display_msg_ + // replace calling debug_printf_ with _debug_func, to avoid buffer overflow with messages longer than 260 bytes + MakeCall(0x45540F, op_display_msg_hack); + MakeCall(0x45CB4E, op_debug_msg_hack); // set the position of the debug window SafeWrite8(0x4DC34D, 15); @@ -515,6 +538,12 @@ void CheckTimerResolution() { void DebugEditor::init() { DebugModePatch(); + // Prints a debug message about a missing critter art file to debug.log + HookCall(0x419B65, art_data_size_hook); + // Checks the animation code, if ANIM_walk then skip printing the debug message + HookCall(0x419AA0, art_data_size_hook_check); + SafeWrite8(0x419B61, CodeType::JumpNZ); // jz > jnz + // Notifies and prints a debug message about a corrupted proto file to debug.log MakeCall(0x4A1D73, proto_load_pid_hack, 6); @@ -538,7 +567,7 @@ void DebugEditor::init() { if (mapGridToggleKey) { OnKeyPressed() += [](DWORD scanCode, bool pressed) { if (scanCode == mapGridToggleKey && pressed && IsGameLoaded()) { - __asm call fo::funcoffs::grid_toggle_; + fo::func::grid_toggle(); fo::func::tile_refresh_display(); } }; diff --git a/sfall/Modules/DebugEditor.h b/sfall/Modules/DebugEditor.h index 4390b4eef..34dda8ec9 100644 --- a/sfall/Modules/DebugEditor.h +++ b/sfall/Modules/DebugEditor.h @@ -18,6 +18,8 @@ #pragma once +#include "Module.h" + namespace sfall { diff --git a/sfall/Modules/Drugs.cpp b/sfall/Modules/Drugs.cpp index d5beabd76..e1df62320 100644 --- a/sfall/Modules/Drugs.cpp +++ b/sfall/Modules/Drugs.cpp @@ -283,11 +283,12 @@ long Drugs::SetDrugAddictTimeOff(long pid, long time) { } void Drugs::init() { - auto drugsFile = IniReader::GetConfigString("Misc", "DrugsFile", "", MAX_PATH); + auto drugsFile = IniReader::GetConfigString("Misc", "DrugsFile", ""); if (!drugsFile.empty()) { - dlog("Applying drugs patch...", DL_INIT); const char* iniDrugs = drugsFile.insert(0, ".\\").c_str(); + if (GetFileAttributesA(iniDrugs) == INVALID_FILE_ATTRIBUTES) return; + dlog("Applying drugs patch...", DL_INIT); JetWithdrawal = (IniReader::GetInt("main", "JetWithdrawal", 0, iniDrugs) == 1); // SafeWrite8(0x47A3A8, 0); item_wd_process_ int count = IniReader::GetInt("main", "Count", 0, iniDrugs); diff --git a/sfall/Modules/Elevators.cpp b/sfall/Modules/Elevators.cpp index 8c1fbdb52..37346eb7f 100644 --- a/sfall/Modules/Elevators.cpp +++ b/sfall/Modules/Elevators.cpp @@ -113,27 +113,25 @@ static void LoadElevators(const char* elevFile) { for (int i = 0; i < vanillaElevatorCount; i++) elevatorType[i] = i; char section[4]; - if (elevFile && GetFileAttributes(elevFile) != INVALID_FILE_ATTRIBUTES) { - for (int i = 0; i < elevatorCount; i++) { - _itoa_s(i, section, 10); - int type = IniReader::GetInt(section, "Image", elevatorType[i], elevFile); - elevatorType[i] = min(type, elevatorCount - 1); - if (i >= vanillaElevatorCount) { - int cBtn = IniReader::GetInt(section, "ButtonCount", 2, elevFile); - if (cBtn < 2) cBtn = 2; - elevatorsBtnCount[i] = min(cBtn, exitsPerElevator); - } - elevatorsFrms[i].main = IniReader::GetInt(section, "MainFrm", elevatorsFrms[i].main, elevFile); - elevatorsFrms[i].buttons = IniReader::GetInt(section, "ButtonsFrm", elevatorsFrms[i].buttons, elevFile); - char setting[32]; - for (int j = 0; j < exitsPerElevator; j++) { - sprintf(setting, "ID%d", j + 1); - elevatorExits[i][j].id = IniReader::GetInt(section, setting, elevatorExits[i][j].id, elevFile); - sprintf(setting, "Elevation%d", j + 1); - elevatorExits[i][j].elevation = IniReader::GetInt(section, setting, elevatorExits[i][j].elevation, elevFile); - sprintf(setting, "Tile%d", j + 1); - elevatorExits[i][j].tile = IniReader::GetInt(section, setting, elevatorExits[i][j].tile, elevFile); - } + for (int i = 0; i < elevatorCount; i++) { + _itoa_s(i, section, 10); + int type = IniReader::GetInt(section, "Image", elevatorType[i], elevFile); + elevatorType[i] = min(type, elevatorCount - 1); + if (i >= vanillaElevatorCount) { + int cBtn = IniReader::GetInt(section, "ButtonCount", 2, elevFile); + if (cBtn < 2) cBtn = 2; + elevatorsBtnCount[i] = min(cBtn, exitsPerElevator); + } + elevatorsFrms[i].main = IniReader::GetInt(section, "MainFrm", elevatorsFrms[i].main, elevFile); + elevatorsFrms[i].buttons = IniReader::GetInt(section, "ButtonsFrm", elevatorsFrms[i].buttons, elevFile); + char setting[32]; + for (int j = 0; j < exitsPerElevator; j++) { + sprintf(setting, "ID%d", j + 1); + elevatorExits[i][j].id = IniReader::GetInt(section, setting, elevatorExits[i][j].id, elevFile); + sprintf(setting, "Elevation%d", j + 1); + elevatorExits[i][j].elevation = IniReader::GetInt(section, setting, elevatorExits[i][j].elevation, elevFile); + sprintf(setting, "Tile%d", j + 1); + elevatorExits[i][j].tile = IniReader::GetInt(section, setting, elevatorExits[i][j].tile, elevFile); } } } @@ -159,11 +157,14 @@ static void ElevatorsInit() { } void Elevators::init() { - auto elevPath = IniReader::GetConfigString("Misc", "ElevatorsFile", "", MAX_PATH); + auto elevPath = IniReader::GetConfigString("Misc", "ElevatorsFile", ""); if (!elevPath.empty()) { + const char* elevFile = elevPath.insert(0, ".\\").c_str(); + if (GetFileAttributesA(elevFile) == INVALID_FILE_ATTRIBUTES) return; + dlogr("Applying elevator patch.", DL_INIT); ElevatorsInit(); - LoadElevators(elevPath.insert(0, ".\\").c_str()); + LoadElevators(elevFile); } } diff --git a/sfall/Modules/EngineTweaks.cpp b/sfall/Modules/EngineTweaks.cpp index 993ec7d01..5df0761f4 100644 --- a/sfall/Modules/EngineTweaks.cpp +++ b/sfall/Modules/EngineTweaks.cpp @@ -27,9 +27,10 @@ namespace sfall { void EngineTweaks::init() { - auto tweaksFile = IniReader::GetConfigString("Misc", "TweaksFile", "", MAX_PATH); + auto tweaksFile = IniReader::GetConfigString("Misc", "TweaksFile", ""); if (!tweaksFile.empty()) { const char* cTweaksFile = tweaksFile.insert(0, ".\\").c_str(); + if (GetFileAttributesA(cTweaksFile) == INVALID_FILE_ATTRIBUTES) return; game::Items::SetHealingPID(0, IniReader::GetInt("Items", "STIMPAK", fo::PID_STIMPAK, cTweaksFile)); game::Items::SetHealingPID(1, IniReader::GetInt("Items", "SUPER_STIMPAK", fo::PID_SUPER_STIMPAK, cTweaksFile)); diff --git a/sfall/Modules/EngineTweaks.h b/sfall/Modules/EngineTweaks.h index 3099edb81..7c883f47d 100644 --- a/sfall/Modules/EngineTweaks.h +++ b/sfall/Modules/EngineTweaks.h @@ -27,7 +27,6 @@ class EngineTweaks : public Module { public: const char* name() { return "EngineTweaks"; } void init(); - //void exit() override; }; } diff --git a/sfall/Modules/Graphics.cpp b/sfall/Modules/Graphics.cpp index dc8a7595a..1f24c4fbc 100644 --- a/sfall/Modules/Graphics.cpp +++ b/sfall/Modules/Graphics.cpp @@ -1311,10 +1311,11 @@ void Graphics::init() { SafeWriteBatch(10000, {0x4C908B, 0x4C9093}); // default_screendump_ WindowRender::init(); + + LoadGameHook::OnBeforeGameClose() += []() { WinProc::SavePosition(Graphics::mode); }; } void Graphics::exit() { - WinProc::SavePosition(Graphics::mode); if (Graphics::mode >= 4) { CoUninitialize(); } else { diff --git a/sfall/Modules/HeroAppearance.cpp b/sfall/Modules/HeroAppearance.cpp index e2b59fb51..869e272c7 100644 --- a/sfall/Modules/HeroAppearance.cpp +++ b/sfall/Modules/HeroAppearance.cpp @@ -179,12 +179,12 @@ static __declspec(noinline) int __stdcall LoadHeroDat(unsigned int race, unsigne bool folderExists = false, heroDatExists = false; // check if folder exists for selected appearance sprintf_s(heroPathPtr[0]->path, 64, appearancePathFmt, sex, race, style, ""); - if (GetFileAttributes(heroPathPtr[0]->path) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributesA(heroPathPtr[0]->path) != INVALID_FILE_ATTRIBUTES) { folderExists = true; } // check if Dat exists for selected appearance sprintf_s(heroPathPtr[1]->path, 64, appearancePathFmt, sex, race, style, ".dat"); - int result = GetFileAttributes(heroPathPtr[1]->path); + int result = GetFileAttributesA(heroPathPtr[1]->path); if (result != INVALID_FILE_ATTRIBUTES) { if (!(result & FILE_ATTRIBUTE_DIRECTORY)) { heroPathPtr[1]->pDat = LoadDat(heroPathPtr[1]->path); @@ -203,12 +203,12 @@ static __declspec(noinline) int __stdcall LoadHeroDat(unsigned int race, unsigne bool raceDatExists = false, folderExists = false; // check if folder exists for selected race base appearance sprintf_s(racePathPtr[0]->path, 64, appearancePathFmt, sex, race, 0, ""); - if (GetFileAttributes(racePathPtr[0]->path) != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributesA(racePathPtr[0]->path) != INVALID_FILE_ATTRIBUTES) { folderExists = true; } // check if Dat (or folder) exists for selected race base appearance sprintf_s(racePathPtr[1]->path, 64, appearancePathFmt, sex, race, 0, ".dat"); - int result = GetFileAttributes(racePathPtr[1]->path); + int result = GetFileAttributesA(racePathPtr[1]->path); if (result != INVALID_FILE_ATTRIBUTES) { if (!(result & FILE_ATTRIBUTE_DIRECTORY)) { racePathPtr[1]->pDat = LoadDat(racePathPtr[1]->path); @@ -1408,13 +1408,13 @@ static void EnableHeroAppearanceMod() { racePathPtr[1]->path = new char[64]; // check if Data exists for other races male or female, and if so enable race selection buttons - if (GetFileAttributes("Appearance\\hmR01S00") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR01S00") != INVALID_FILE_ATTRIBUTES || - GetFileAttributes("Appearance\\hmR01S00.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR01S00.dat") != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributesA("Appearance\\hmR01S00") != INVALID_FILE_ATTRIBUTES || GetFileAttributesA("Appearance\\hfR01S00") != INVALID_FILE_ATTRIBUTES || + GetFileAttributesA("Appearance\\hmR01S00.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributesA("Appearance\\hfR01S00.dat") != INVALID_FILE_ATTRIBUTES) { raceButtons = true; } // check if Data exists for other styles male or female, and if so enable style selection buttons - if (GetFileAttributes("Appearance\\hmR00S01") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR00S01") != INVALID_FILE_ATTRIBUTES || - GetFileAttributes("Appearance\\hmR00S01.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributes("Appearance\\hfR00S01.dat") != INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributesA("Appearance\\hmR00S01") != INVALID_FILE_ATTRIBUTES || GetFileAttributesA("Appearance\\hfR00S01") != INVALID_FILE_ATTRIBUTES || + GetFileAttributesA("Appearance\\hmR00S01.dat") != INVALID_FILE_ATTRIBUTES || GetFileAttributesA("Appearance\\hfR00S01.dat") != INVALID_FILE_ATTRIBUTES) { styleButtons = true; } diff --git a/sfall/Modules/HeroAppearance.h b/sfall/Modules/HeroAppearance.h index 103be01f6..9a8b4957c 100644 --- a/sfall/Modules/HeroAppearance.h +++ b/sfall/Modules/HeroAppearance.h @@ -26,7 +26,6 @@ namespace sfall class HeroAppearance : public Module { public: const char* name() { return "HeroAppearance"; } - void init(); void exit() override; diff --git a/sfall/Modules/HookScripts/CombatHs.cpp b/sfall/Modules/HookScripts/CombatHs.cpp index bdec6da55..66ba4d2fc 100644 --- a/sfall/Modules/HookScripts/CombatHs.cpp +++ b/sfall/Modules/HookScripts/CombatHs.cpp @@ -36,6 +36,12 @@ static void __declspec(naked) ToHitHook() { __asm { cmp cRet, 1; cmovge ebx, rets[0]; + mov eax, 999; // max + cmp ebx, eax; + cmovg ebx, eax; + mov eax, -99; // min + cmp ebx, eax; + cmovl ebx, eax; call EndHook; mov eax, ebx; retn 8; @@ -350,7 +356,7 @@ int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD RunHookScript(HOOK_AMMOCOST); - if (cRet > 0) rounds = rets[0]; // override rounds + if (cRet > 0 && (long)rets[0] >= 0) rounds = rets[0]; // override rounds failed: EndHook(); @@ -670,13 +676,13 @@ static void __declspec(naked) CanUseWeaponHook() { retn; } } - +/* bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode) { return (HookScripts::HookHasScript(HOOK_CANUSEWEAPON)) ? CanUseWeaponHook_Script(result, source, weapon, hitMode) : result; } - +*/ //////////////////////////////////////////////////////////////////////////////// void Inject_ToHitHook() { @@ -721,7 +727,7 @@ void Inject_CombatDamageHook() { // 0x423DE7, // compute_explosion_on_extras() 0x423E69, // compute_explosion_on_extras() 0x424220, // attack_crit_failure() - 0x4242FB, // attack_crit_failure() + 0x4242FB // attack_crit_failure() }); MakeCall(0x423DEB, ComputeDamageHook); // compute_explosion_on_extras() - fix for the attacker } diff --git a/sfall/Modules/HookScripts/CombatHs.h b/sfall/Modules/HookScripts/CombatHs.h index 941447ec5..d9fc6c45b 100644 --- a/sfall/Modules/HookScripts/CombatHs.h +++ b/sfall/Modules/HookScripts/CombatHs.h @@ -26,6 +26,6 @@ int __fastcall AmmoCostHook_Script(DWORD hookType, fo::GameObject* weapon, DWORD long CalcApCostHook_Invoke(fo::GameObject* source, long hitMode, long isCalled, long cost, fo::GameObject* weapon); //void FindTargetHook_Invoke(fo::GameObject* targets[], fo::GameObject* attacker); //fo::GameObject* BestWeaponHook_Invoke(fo::GameObject* bestWeapon, fo::GameObject* source, fo::GameObject* weapon1, fo::GameObject* weapon2, fo::GameObject* target); -bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode); +//bool CanUseWeaponHook_Invoke(bool result, fo::GameObject* source, fo::GameObject* weapon, long hitMode); } diff --git a/sfall/Modules/HookScripts/Common.cpp b/sfall/Modules/HookScripts/Common.cpp index 404b96f62..f6e7787b0 100644 --- a/sfall/Modules/HookScripts/Common.cpp +++ b/sfall/Modules/HookScripts/Common.cpp @@ -3,6 +3,8 @@ #include "Common.h" +using namespace sfall::script; + namespace sfall { @@ -12,17 +14,21 @@ constexpr int maxDepth = 8; // Maximum recursion depth for hook calls struct { DWORD hookID; + bool allowNonIntReturn; DWORD argCount; DWORD cArg; DWORD cRet; DWORD cRetTmp; - DWORD oldArgs[maxArgs]; - DWORD oldRets[maxRets]; + DWORD args[maxArgs]; + DWORD rets[maxRets]; + DataType retTypes[maxRets]; } savedArgs[maxDepth]; static DWORD callDepth; static DWORD currentRunHook = -1; +bool allowNonIntReturn; +DataType retTypes[maxRets]; // current hook return value types DWORD args[maxArgs]; // current hook arguments DWORD rets[maxRets]; // current hook return values @@ -37,25 +43,27 @@ DWORD HookCommon::GetHSArgCount() { return argCount; } -DWORD HookCommon::GetHSArg() { - return (cArg == argCount) ? 0 : args[cArg++]; -} - -void HookCommon::SetHSArg(DWORD id, DWORD value) { - if (id < argCount) args[id] = value; +ScriptValue HookCommon::GetHSArg() { + return (cArg == argCount) ? 0 : GetHSArgAt(cArg++); } -DWORD* HookCommon::GetHSArgs() { - return args; +void HookCommon::SetHSArg(DWORD id, const ScriptValue& value) { + if (id < argCount) { + args[id] = value.rawValue(); + } } -DWORD HookCommon::GetHSArgAt(DWORD id) { +ScriptValue HookCommon::GetHSArgAt(DWORD id) { return args[id]; } -void __stdcall HookCommon::SetHSReturn(DWORD value) { +void HookCommon::SetHSReturn(const ScriptValue& value) { + // For backward compatibility - ignore non-int return values + if (!allowNonIntReturn && !value.isInt()) return; + if (cRetTmp < maxRets) { - rets[cRetTmp++] = value; + retTypes[cRetTmp] = value.type(); + rets[cRetTmp++] = value.rawValue(); } if (cRetTmp > cRet) { cRet = cRetTmp; @@ -81,12 +89,16 @@ void __stdcall BeginHook() { // save all values of the current hook if another hook was called during the execution of the current hook int cDepth = callDepth - 1; savedArgs[cDepth].hookID = currentRunHook; + savedArgs[cDepth].allowNonIntReturn = allowNonIntReturn; savedArgs[cDepth].argCount = argCount; // number of arguments of the current hook savedArgs[cDepth].cArg = cArg; // current count of taken arguments savedArgs[cDepth].cRet = cRet; // number of return values for the current hook savedArgs[cDepth].cRetTmp = cRetTmp; - std::memcpy(&savedArgs[cDepth].oldArgs, args, maxArgs * sizeof(DWORD)); // values of the arguments - if (cRet) std::memcpy(&savedArgs[cDepth].oldRets, rets, maxRets * sizeof(DWORD)); // return values + std::memcpy(&savedArgs[cDepth].args, args, maxArgs * sizeof(DWORD)); // values of the arguments + if (cRet) { + std::memcpy(&savedArgs[cDepth].rets, rets, maxRets * sizeof(DWORD)); // return values + std::memcpy(&savedArgs[cDepth].retTypes, retTypes, maxRets * sizeof(DataType)); // return value types + } //devlog_f("\nSaved cArgs/cRet: %d / %d(%d)\n", DL_HOOK, savedArgs[cDepth].argCount, savedArgs[cDepth].cRet, cRetTmp); //for (unsigned int i = 0; i < maxArgs; i++) { @@ -94,6 +106,7 @@ void __stdcall BeginHook() { //} } callDepth++; + allowNonIntReturn = false; devlog_f("Begin running hook, current depth: %d, current executable hook: %d\n", DL_HOOK, callDepth, currentRunHook); } @@ -146,12 +159,16 @@ void __stdcall EndHook() { // restore all saved values of the previous hook int cDepth = callDepth - 1; currentRunHook = savedArgs[cDepth].hookID; + allowNonIntReturn = savedArgs[cDepth].allowNonIntReturn; argCount = savedArgs[cDepth].argCount; cArg = savedArgs[cDepth].cArg; cRet = savedArgs[cDepth].cRet; cRetTmp = savedArgs[cDepth].cRetTmp; // also restore current count of the number of return values - std::memcpy(args, &savedArgs[cDepth].oldArgs, maxArgs * sizeof(DWORD)); - if (cRet) std::memcpy(rets, &savedArgs[cDepth].oldRets, maxRets * sizeof(DWORD)); + std::memcpy(args, &savedArgs[cDepth].args, maxArgs * sizeof(DWORD)); + if (cRet > 0) { + std::memcpy(rets, &savedArgs[cDepth].rets, maxRets * sizeof(DWORD)); + std::memcpy(retTypes, &savedArgs[cDepth].retTypes, maxRets * sizeof(DataType)); + } //devlog_f("Restored cArgs/cRet: %d / %d(%d)\n", DL_HOOK, argCount, cRet, cRetTmp); //for (unsigned int i = 0; i < maxArgs; i++) { diff --git a/sfall/Modules/HookScripts/Common.h b/sfall/Modules/HookScripts/Common.h index a093849db..5dc78c7b1 100644 --- a/sfall/Modules/HookScripts/Common.h +++ b/sfall/Modules/HookScripts/Common.h @@ -2,6 +2,7 @@ #include "..\HookScripts.h" #include "..\ScriptExtender.h" +#include "..\Scripting\ScriptValue.h" // Common variables and functions for hook script implementations @@ -11,11 +12,10 @@ namespace sfall class HookCommon { public: static DWORD GetHSArgCount(); - static DWORD GetHSArg(); - static DWORD GetHSArgAt(DWORD id); - static DWORD* GetHSArgs(); - static void SetHSArg(DWORD id, DWORD value); - static void __stdcall SetHSReturn(DWORD d); + static script::ScriptValue GetHSArg(); + static script::ScriptValue GetHSArgAt(DWORD id); + static void SetHSArg(DWORD id, const script::ScriptValue& value); + static void SetHSReturn(const script::ScriptValue& value); static void GameModeChangeHook(DWORD exit); static void __stdcall KeyPressHook(DWORD* dxKey, bool pressed, DWORD vKey); @@ -34,11 +34,12 @@ struct HookScript { // All currently registered hook scripts extern std::vector hooks[]; +extern bool allowNonIntReturn; // allow set_sfall_return with non-int values (validate value in the hook code) +extern script::DataType retTypes[]; // current hook return value types extern DWORD args[]; // current hook arguments extern DWORD rets[]; // current hook return values extern DWORD argCount; -extern DWORD cArg; // how many arguments were taken by current hook script extern DWORD cRet; // how many return values were set by current hook script extern DWORD cRetTmp; // how many return values were set by specific hook script (when using register_hook) diff --git a/sfall/Modules/HookScripts/InventoryHs.cpp b/sfall/Modules/HookScripts/InventoryHs.cpp index da9b8c03a..13a2a6e02 100644 --- a/sfall/Modules/HookScripts/InventoryHs.cpp +++ b/sfall/Modules/HookScripts/InventoryHs.cpp @@ -31,7 +31,7 @@ void RemoveInvenObjHook_Invoke(fo::GameObject* source, fo::GameObject* item, lon static long rmObjType = -1; -void SetRemoveObjectType(long rmType) { +void __stdcall SetRemoveObjectType(long rmType) { rmObjType = rmType; } @@ -657,15 +657,11 @@ void Inject_MoveCostHook() { HookCalls(MoveCostHook, { 0x417665, 0x44B88A }); } -void Inject_SwitchHandHook() { +void Inject_InventoryMoveHook() { HookCalls(SwitchHandHook, { 0x4712E3, // left slot 0x47136D // right slot }); -} - -void Inject_InventoryMoveHook() { - Inject_SwitchHandHook(); MakeJump(0x4713A9, UseArmorHack); // old 0x4713A3 MakeJump(0x476491, DropIntoContainerHack); MakeJumps(DropIntoContainerHandSlotHack, { 0x471338, 0x4712AB }); diff --git a/sfall/Modules/HookScripts/InventoryHs.h b/sfall/Modules/HookScripts/InventoryHs.h index 188d7ad2b..00ab5e561 100644 --- a/sfall/Modules/HookScripts/InventoryHs.h +++ b/sfall/Modules/HookScripts/InventoryHs.h @@ -9,7 +9,6 @@ void InitInventoryHookScripts(); void Inject_RemoveInvenObjHook(); void Inject_MoveCostHook(); -void Inject_SwitchHandHook(); void Inject_InventoryMoveHook(); void Inject_InvenWieldHook(); @@ -18,6 +17,6 @@ long InvenWieldHook_Invoke(fo::GameObject* critter, fo::GameObject* item, long f void InvenUnwield_HookDrop(); void InvenUnwield_HookMove(); -void SetRemoveObjectType(long rmType); +void __stdcall SetRemoveObjectType(long rmType); } diff --git a/sfall/Modules/HookScripts/MiscHs.cpp b/sfall/Modules/HookScripts/MiscHs.cpp index 1805558fe..9cbc70a4a 100644 --- a/sfall/Modules/HookScripts/MiscHs.cpp +++ b/sfall/Modules/HookScripts/MiscHs.cpp @@ -202,31 +202,68 @@ static void __declspec(naked) UseSkillHook() { } } +static long stealExpOverride; + +static void __declspec(naked) StealHook_ExpOverrideHack() { + __asm { + mov ecx, [esp + 0x150 - 0x18 + 4]; // total exp + cmp stealExpOverride, -1; + jle vanillaExp; + add ecx, stealExpOverride; // add overridden exp value + jmp end; +vanillaExp: + add ecx, edi; // add vanilla exp value +end: + add edi, 10; // vanilla exp increment for next success + mov [esp + 0x150 - 0x18 + 4], ecx; // set total exp + mov ecx, [esp]; + add ecx, 14; // shift return address + mov [esp], ecx; + retn; + } +} + static void __declspec(naked) StealCheckHook() { + static const DWORD StealSkipRet = 0x474B18; __asm { HookBegin; mov args[0], eax; // thief mov args[4], edx; // target mov args[8], ebx; // item mov args[12], ecx; // is planting + mov args[16], esi; // quantity pushadc; } - argCount = 4; + argCount = 5; + stealExpOverride = -1; RunHookScript(HOOK_STEAL); __asm { popadc; - cmp cRet, 1; - jl defaultHandler; - cmp rets[0], -1; - je defaultHandler; - mov eax, rets[0]; + cmp cRet, 1; + jl defaultHandler; // no return values, use vanilla path + cmp cRet, 2; + jl skipExpOverride; + push eax; + mov eax, rets[4]; // override experience points for steal + mov stealExpOverride, eax; + pop eax; +skipExpOverride: + cmp rets[0], -1; // if <= -1, use vanilla path + jle defaultHandler; + cmp rets[0], 2; // 2 - steal failed but didn't get cought + jnz normalReturn; + HookEnd; + add esp, 4; + jmp StealSkipRet; +normalReturn: + mov eax, rets[0]; HookEnd; retn; defaultHandler: HookEnd; - jmp fo::funcoffs::skill_check_stealing_; + jmp fo::funcoffs::skill_check_stealing_; } } @@ -634,21 +671,30 @@ static void __declspec(naked) wmRndEncounterOccurred_hook() { static long __stdcall RollCheckHook_Script(long roll, long chance, long bonus, long randomChance, long calledFrom) { long hookType; switch (calledFrom - 5) { - case 0x42388E: // compute_attack_ - // compute_spray_ - case 0x4234D1: hookType = 1; break; // single and burst attack hit event - // compute_spray_ - case 0x42356C: hookType = 2; break; // burst attack bullet hit event - // skill_result_ - case 0x4AAB29: hookType = 3; break; // common skill check event - // skill_use_ - case 0x4AB3B6: hookType = 4; break; // SKILL_REPAIR - case 0x4AB8B5: hookType = 5; break; // SKILL_DOCTOR - // skill_check_stealing_ // SKILL_STEAL - case 0x4ABC9F: hookType = 6; break; // source stealing check event - case 0x4ABCE6: hookType = 7; break; // target stealing check event (fail for success stealing) + case 0x42388E: // compute_attack_ + case 0x4234D1: // compute_spray_ + hookType = 1; // single and burst attack hit event + break; + case 0x42356C: // compute_spray_ + hookType = 2; // burst attack bullet hit event + break; + case 0x4AAB29: // skill_result_ + hookType = 3; // common skill check event + break; + case 0x4AB3B6: // skill_use_ + hookType = 4; // SKILL_REPAIR + break; + case 0x4AB8B5: // skill_use_ + hookType = 5; // SKILL_DOCTOR + break; + case 0x4ABC9F: // skill_check_stealing_ + hookType = 6; // SKILL_STEAL - source stealing check event + break; + case 0x4ABCE6: // skill_check_stealing_ + hookType = 7; // SKILL_STEAL - target stealing check event (fail for success stealing) + break; default: - return roll; // unsupported hook + return roll; // unsupported hook } BeginHook(); @@ -698,6 +744,7 @@ void Inject_UseSkillHook() { void Inject_StealCheckHook() { HookCalls(StealCheckHook, { 0x4749A2, 0x474A69 }); + MakeCalls(StealHook_ExpOverrideHack, { 0x4742C5, 0x4743E1 }); } void Inject_SneakCheckHook() { diff --git a/sfall/Modules/HookScripts/ObjectHs.cpp b/sfall/Modules/HookScripts/ObjectHs.cpp index e932dee88..36bb3f5a0 100644 --- a/sfall/Modules/HookScripts/ObjectHs.cpp +++ b/sfall/Modules/HookScripts/ObjectHs.cpp @@ -6,6 +6,8 @@ #include "ObjectHs.h" +using namespace sfall::script; + // Object hook scripts namespace sfall { @@ -157,16 +159,19 @@ static void __declspec(naked) UseAnimateObjHook() { static DWORD __fastcall DescriptionObjHook_Script(DWORD object) { BeginHook(); + allowNonIntReturn = true; argCount = 1; args[0] = object; RunHookScript(HOOK_DESCRIPTIONOBJ); - DWORD textPrt = (cRet > 0) ? rets[0] : 0; - EndHook(); + DWORD textPtr = cRet > 0 && (retTypes[0] == DataType::INT || retTypes[0] == DataType::STR) + ? rets[0] + : 0; - return textPrt; + EndHook(); + return textPtr; } static void __declspec(naked) DescriptionObjHook() { @@ -254,11 +259,13 @@ static void __declspec(naked) SetMapLightHook() { static DWORD __fastcall StdProcedureHook_Script(long numHandler, fo::ScriptInstance* script, DWORD procTable) { BeginHook(); - argCount = 4; + argCount = 6; args[0] = numHandler; args[1] = (DWORD)script->selfObject; args[2] = (DWORD)script->sourceObject; + args[4] = (DWORD)script->targetObject; + args[5] = script->fixedParam; if (procTable) { args[3] = 0; @@ -373,14 +380,14 @@ void __declspec(naked) critter_adjust_rads_hack() { using namespace Fields; __asm { cmp dword ptr [eax + protoId], PID_Player; // critter.pid - jne isNotDude; + jne notDude; push ecx; call AdjustRads_Script; // ecx - critter, edx - amount pop ecx; mov ebx, eax; // old/new amount mov edx, ds:[FO_VAR_obj_dude]; xor eax, eax; // for continue func -isNotDude: +notDude: retn; } } diff --git a/sfall/Modules/Interface.cpp b/sfall/Modules/Interface.cpp index 19084d425..1dab39087 100644 --- a/sfall/Modules/Interface.cpp +++ b/sfall/Modules/Interface.cpp @@ -599,7 +599,7 @@ static void AddNewDot() { } static void __declspec(naked) DrawingDots() noexcept { - long x_offset, y_offset; + long x_offset, y_offset; __asm { mov ebp, esp; // prolog sub esp, __LOCAL_SIZE; @@ -882,7 +882,7 @@ static void WorldMapInterfacePatch() { if (color > 228) color = 228; else if (color < 1) color = 1; // no palette animation colors colorDot = color; - auto dotList = IniReader::GetConfigList("Interface", "TravelMarkerStyles", "", 512); + auto dotList = IniReader::GetConfigList("Interface", "TravelMarkerStyles", ""); if (!dotList.empty()) { terrainCount = dotList.size(); dotStyle = new DotStyle[terrainCount]; diff --git a/sfall/Modules/Inventory.cpp b/sfall/Modules/Inventory.cpp index 38d1b4c5e..26ca69cae 100644 --- a/sfall/Modules/Inventory.cpp +++ b/sfall/Modules/Inventory.cpp @@ -556,6 +556,28 @@ static void __declspec(naked) op_wield_obj_critter_hook() { } } +// Allow passing non-weapon/armor items to invenWieldFunc_ engine function without error +// It used to treat all non-armor items as "weapon" and try to check if critter had weapon animation, +// which isn't valid for other item subtypes. +static void __declspec(naked) invenWieldFunc_hack() { + static const DWORD invenWieldFunc_hack_back = 0x47285D; + static const DWORD invenWieldFunc_hack_skip = 0x4728A7; + using namespace fo; + using namespace Fields; + __asm { + mov eax, edi; // weapon + call fo::funcoffs::item_get_type_; + cmp eax, item_type_weapon; + je isWeapon; + jmp invenWieldFunc_hack_skip; +isWeapon: // overwritten engine code + mov eax, [esi + rotation]; // cur_rot + inc eax; + push eax; + jmp invenWieldFunc_hack_back; + } +} + static void __declspec(naked) do_move_timer_hook() { static const DWORD DoMoveTimer_Ret = 0x476920; __asm { @@ -742,6 +764,9 @@ void Inventory::init() { // Note: the flag is not checked for the metarule(METARULE_INVEN_UNWIELD_WHO, x) function HookCall(0x45B0CE, op_inven_unwield_hook); // with fix to update interface slot after unwielding HookCall(0x45693C, op_wield_obj_critter_hook); + + // Fix for invenWieldFunc_ (used by wield_obj_critter) to be able to put non-weapon/armor items into active slot + MakeJump(0x472858, invenWieldFunc_hack); } void Inventory::InvokeAdjustFid(long fid) { diff --git a/sfall/Modules/Karma.cpp b/sfall/Modules/Karma.cpp index 8513e10f2..daa9471ea 100644 --- a/sfall/Modules/Karma.cpp +++ b/sfall/Modules/Karma.cpp @@ -43,7 +43,7 @@ bool displayKarmaChanges; static DWORD __stdcall DrawCard() { int reputation = fo::var::game_global_vars[fo::GVAR_PLAYER_REPUTATION]; - for (auto& info : karmaFrms) { + for (const auto& info : karmaFrms) { if (reputation < info.points) { return info.frm; } @@ -88,11 +88,11 @@ static void ApplyDisplayKarmaChangesPatch() { } static void ApplyKarmaFRMsPatch() { - auto karmaFrmList = IniReader::GetConfigList("Misc", "KarmaFRMs", "", 512); + auto karmaFrmList = IniReader::GetConfigList("Misc", "KarmaFRMs", ""); size_t countFrm = karmaFrmList.size(); if (countFrm) { dlogr("Applying karma FRM patch.", DL_INIT); - auto karmaPointsList = IniReader::GetConfigList("Misc", "KarmaPoints", "", 512); + auto karmaPointsList = IniReader::GetConfigList("Misc", "KarmaPoints", ""); karmaFrms.resize(countFrm); size_t countPoints = karmaPointsList.size(); diff --git a/sfall/Modules/LoadGameHook.cpp b/sfall/Modules/LoadGameHook.cpp index 06ca90145..aa6d5c6c7 100644 --- a/sfall/Modules/LoadGameHook.cpp +++ b/sfall/Modules/LoadGameHook.cpp @@ -39,6 +39,8 @@ #include "LoadGameHook.h" +#include + namespace sfall { @@ -72,10 +74,23 @@ static DWORD saveInCombatFix; static bool gameLoaded = false; static bool onLoadingMap = false; +static std::chrono::time_point timeBeforeGameInit; +static std::chrono::time_point timeBeforeGameStart; + char LoadGameHook::mapLoadingName[16]; // current loading/loaded map name long LoadGameHook::interfaceWID = -1; +void saveTimeIntervalStart(std::chrono::time_point& startTime) { + startTime = std::chrono::high_resolution_clock::now(); +} + +void logTimeInterval(const char* action, const std::chrono::time_point& startTime) { + auto stopTime = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(stopTime - startTime); + dlog_f("%s in: %d us\n", DL_MAIN, action, duration.count()); +} + bool LoadGameHook::IsMapLoading() { return onLoadingMap; } @@ -226,6 +241,7 @@ static void __declspec(naked) SaveGame_hook() { // Called right before savegame slot is being loaded static bool LoadGame_Before() { + saveTimeIntervalStart(timeBeforeGameStart); onBeforeGameStart.invoke(); char buf[MAX_PATH]; @@ -288,6 +304,7 @@ static bool __stdcall GameReset(DWORD isGameLoad) { static void __stdcall LoadGame_After() { onAfterGameStarted.invoke(); gameLoaded = true; + logTimeInterval("Game loaded", timeBeforeGameStart); } static void __declspec(naked) LoadGame_hook() { @@ -320,14 +337,15 @@ static void __declspec(naked) EndLoadHook() { } static void __stdcall NewGame_Before() { + saveTimeIntervalStart(timeBeforeGameStart); onBeforeGameStart.invoke(); } static void __stdcall NewGame_After() { - dlogr("New Game started.", DL_MAIN); onAfterNewGame.invoke(); onAfterGameStarted.invoke(); gameLoaded = true; + logTimeInterval("New Game started", timeBeforeGameStart); } static void __declspec(naked) main_load_new_hook() { @@ -341,6 +359,7 @@ static void __declspec(naked) main_load_new_hook() { } static void __stdcall GameInitialization() { + saveTimeIntervalStart(timeBeforeGameInit); onBeforeGameInit.invoke(); } @@ -356,6 +375,8 @@ static void __stdcall GameInitialized(int initResult) { } #endif onAfterGameInit.invoke(); + + logTimeInterval("Game initalized", timeBeforeGameInit); } static void __stdcall GameExit() { diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index c2f4bec53..67039787f 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -17,6 +17,7 @@ */ #include +#include #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" @@ -263,16 +264,57 @@ static void __fastcall game_init_databases_hook1() { } */ static bool NormalizePath(std::string &path) { - if (path.find(':') != std::string::npos) return false; + const char* whiteSpaces = " \t"; + // Remove comments. + size_t pos = path.find_first_of(";#"); + if (pos != std::string::npos) { + path.erase(pos); + } + // Trim whitespaces. + path.erase(0, path.find_first_not_of(whiteSpaces)); // trim left + path.erase(path.find_last_not_of(whiteSpaces) + 1); // trim right + // Normalize directory separators. + std::replace(path.begin(), path.end(), '/', '\\'); + // Remove leading slashes. + path.erase(0, path.find_first_not_of('\\')); + + // Disallow paths trying to "escape" game folder: + if (path.find(':') != std::string::npos || + path.find(".\\") != std::string::npos || + path.find("..\\") != std::string::npos) { + return false; + } + return !path.empty(); +} - int pos = 0; - do { // replace all '/' char with '\' - pos = path.find('/', pos); - if (pos != std::string::npos) path[pos] = '\\'; - } while (pos != std::string::npos); +static bool FileExists(const std::string& path) { + DWORD dwAttrib = GetFileAttributesA(path.c_str()); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} - if (path.find(".\\") != std::string::npos || path.find("..\\") != std::string::npos) return false; - while (path.front() == '\\') path.erase(0, 1); // remove firsts '\' +static bool FolderExists(const std::string& path) { + DWORD dwAttrib = GetFileAttributesA(path.c_str()); + return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY)); +} + +static bool FileOrFolderExists(const std::string& path) { + return GetFileAttributesA(path.c_str()) != INVALID_FILE_ATTRIBUTES; +} + +static bool ValidateExtraPatch(std::string& path, const char* basePath, const char* entryName) { + if (!NormalizePath(path)) { + if (!path.empty()) { + dlog_f("Error: %s invalid entry: \"%s\"\n", DL_INIT, entryName, path.c_str()); + } + return false; + } + path.insert(0, basePath); + if (!FileOrFolderExists(path)) { + const char* entry = path.c_str(); + if (path.find(".\\") == 0) entry += 2; + dlog_f("Error: %s entry not found: %s\n", DL_INIT, entryName, entry); + return false; + } return true; } @@ -281,33 +323,61 @@ static void GetExtraPatches() { char patchFile[12] = "PatchFile"; for (int i = 0; i < 100; i++) { _itoa(i, &patchFile[9], 10); - auto patch = IniReader::GetConfigString("ExtraPatches", patchFile, "", MAX_PATH); - if (patch.empty() || !NormalizePath(patch) || GetFileAttributes(patch.c_str()) == INVALID_FILE_ATTRIBUTES) continue; + auto patch = IniReader::GetConfigString("ExtraPatches", patchFile, ""); + if (!ValidateExtraPatch(patch, "", patchFile)) continue; patchFiles.push_back(patch); } - std::string searchPath = "mods\\"; //IniReader::GetConfigString("ExtraPatches", "AutoSearchPath", "mods\\", MAX_PATH); - //if (!searchPath.empty() && NormalizePath(searchPath)) { - //if (searchPath.back() != '\\') searchPath += "\\"; - - std::string pathMask(".\\mods\\*.dat"); - size_t startPos = patchFiles.size(); - dlogr("Loading custom patches:", DL_MAIN); - WIN32_FIND_DATA findData; - HANDLE hFind = FindFirstFile(pathMask.c_str(), &findData); - if (hFind != INVALID_HANDLE_VALUE) { - do { - std::string name(searchPath + findData.cFileName); - if ((name.length() - name.find_last_of('.')) > 4) continue; - dlog_f("> %s\n", DL_MAIN, name.c_str()); - patchFiles.push_back(name); - } while (FindNextFile(hFind, &findData)); - FindClose(hFind); + const std::string modsPath = ".\\mods\\"; + const std::string loadOrderFileName = "mods_order.txt"; + const std::string loadOrderFilePath = modsPath + loadOrderFileName; + + // If the mods folder does not exist, create it. + if (!FolderExists(modsPath)) { + dlog_f("Creating Mods folder: %s\n", DL_INIT, modsPath.c_str() + 2); + CreateDirectoryA(modsPath.c_str(), 0); + } + // If load order file does not exist, initialize it automatically with mods already in the mods folder. + if (!FileExists(loadOrderFilePath)) { + dlog_f("Generating Mods Order file based on the contents of Mods folder: %s\n", DL_INIT, loadOrderFilePath.c_str() + 2); + std::ofstream loadOrderFile(loadOrderFilePath, std::ios::out | std::ios::trunc); + if (loadOrderFile.is_open()) { + // Search all .dat files and folders in the mods folder. + std::vector autoLoadedPatchFiles; + std::string pathMask(modsPath + "*.dat"); + WIN32_FIND_DATA findData; + HANDLE hFind = FindFirstFile(pathMask.c_str(), &findData); + if (hFind != INVALID_HANDLE_VALUE) { + do { + std::string name(findData.cFileName); + if ((name.length() - name.find_last_of('.')) > 4) continue; + + autoLoadedPatchFiles.push_back(name); + } while (FindNextFile(hFind, &findData)); + FindClose(hFind); + } + // Sort the search result. + std::sort(autoLoadedPatchFiles.begin(), autoLoadedPatchFiles.end()); + // Write found files into load order file. + for (const auto& filePath : autoLoadedPatchFiles) { + loadOrderFile << filePath << '\n'; + } + } else { + dlog_f("Error creating load order file %s.\n", DL_INIT, loadOrderFilePath.c_str() + 2); } - // Sort the search result - if (!patchFiles.empty()) { - std::sort(patchFiles.begin() + startPos, patchFiles.end()); + } + + // Add mods from load order file. + std::ifstream loadOrderFile(loadOrderFilePath, std::ios::in); + if (loadOrderFile.is_open()) { + std::string patch; + while (std::getline(loadOrderFile, patch)) { + if (!ValidateExtraPatch(patch, modsPath.c_str(), loadOrderFileName.c_str())) continue; + patchFiles.push_back(patch); } - //} + } else { + dlog_f("Error opening %s for read: 0x%x\n", DL_INIT, loadOrderFilePath.c_str() + 2, GetLastError()); + } + // Remove first duplicates size_t size = patchFiles.size(); for (size_t i = 0; i < size; ++i) { @@ -317,6 +387,11 @@ static void GetExtraPatches() { } } } + + dlogr("Loading extra patches:", DL_INIT); + for (const auto& patch : patchFiles) { + dlog_f("> %s\n", DL_INIT, patch.c_str() + 2); + } } static void MultiPatchesPatch() { diff --git a/sfall/Modules/MainLoopHook.h b/sfall/Modules/MainLoopHook.h index b3a55f9f5..19ee86b9f 100644 --- a/sfall/Modules/MainLoopHook.h +++ b/sfall/Modules/MainLoopHook.h @@ -27,7 +27,6 @@ namespace sfall class MainLoopHook : public Module { public: const char* name() { return "MainLoopHook"; } - void init(); // Main game loop (real-time action) diff --git a/sfall/Modules/Message.cpp b/sfall/Modules/Message.cpp index a5fd2dfaf..dda461bbb 100644 --- a/sfall/Modules/Message.cpp +++ b/sfall/Modules/Message.cpp @@ -235,7 +235,7 @@ static void ClearReadExtraGameMsgFiles() { } void Message::init() { - msgFileList = IniReader::GetConfigList("Misc", "ExtraGameMsgFileList", "", 512); + msgFileList = IniReader::GetConfigList("Misc", "ExtraGameMsgFileList", ""); if (IniReader::GetConfigInt("Misc", "DialogGenderWords", 0)) { dlogr("Applying dialog gender words patch.", DL_INIT); diff --git a/sfall/Modules/Message.h b/sfall/Modules/Message.h index 585714144..acb32ec32 100644 --- a/sfall/Modules/Message.h +++ b/sfall/Modules/Message.h @@ -57,7 +57,6 @@ class Message : public Module { public: const char* name() { return "Message"; } void init(); - //void exit() override; static const char* GameLanguage(); diff --git a/sfall/Modules/MetaruleExtender.h b/sfall/Modules/MetaruleExtender.h index eae0c64f0..3e9293d5f 100644 --- a/sfall/Modules/MetaruleExtender.h +++ b/sfall/Modules/MetaruleExtender.h @@ -27,7 +27,6 @@ class MetaruleExtender : public Module { public: const char* name() { return "MetaruleExtender"; } void init(); - //void exit() override; }; diff --git a/sfall/Modules/MiscPatches.cpp b/sfall/Modules/MiscPatches.cpp index 34769a4e2..1035b57ba 100644 --- a/sfall/Modules/MiscPatches.cpp +++ b/sfall/Modules/MiscPatches.cpp @@ -88,12 +88,12 @@ static void __declspec(naked) register_object_take_out_hack() { } static void __declspec(naked) gdAddOptionStr_hack() { + static const DWORD gdAddOptionStr_hack_Ret = 0x4458FA; __asm { mov ecx, ds:[FO_VAR_gdNumOptions]; add ecx, '1'; push ecx; - mov ecx, 0x4458FA; - jmp ecx; + jmp gdAddOptionStr_hack_Ret; } } @@ -254,7 +254,7 @@ static void __fastcall SwapHandSlots(fo::GameObject* item, fo::GameObject* &toSl } std::memcpy(dstSlot, &item, 0x14); } else { // swap slots - auto hands = fo::var::itemButtonItems; + auto& hands = fo::var::itemButtonItems; hands[fo::HandSlot::Left].primaryAttack = fo::AttackType::ATKTYPE_RWEAPON_PRIMARY; hands[fo::HandSlot::Left].secondaryAttack = fo::AttackType::ATKTYPE_RWEAPON_SECONDARY; hands[fo::HandSlot::Right].primaryAttack = fo::AttackType::ATKTYPE_LWEAPON_PRIMARY; @@ -535,9 +535,9 @@ static void BoostScriptDialogLimitPatch() { static void NumbersInDialoguePatch() { if (IniReader::GetConfigInt("Misc", "NumbersInDialogue", 0)) { dlogr("Applying numbers in dialogue patch.", DL_INIT); - SafeWrite32(0x502C32, 0x2000202E); + SafeWrite32(0x502C32, 0x2000202E); // '%c ' > '%c. ' SafeWrite8(0x446F3B, 0x35); - SafeWrite32(0x5029E2, 0x7325202E); + SafeWrite32(0x5029E2, 0x7325202E); // '%c %s' > '%c. %s' SafeWrite32(0x446F03, 0x2424448B); // mov eax, [esp+0x24] SafeWrite8(0x446F07, 0x50); // push eax SafeWrite32(0x446FE0, 0x2824448B); // mov eax, [esp+0x28] @@ -991,6 +991,13 @@ void MiscPatches::init() { // Remove an old floating message when creating a new one if the maximum number of floating messages has been reached HookCall(0x4B03A1, text_object_create_hack); // jge hack + // Increase the maximum value of the combat speed slider from 50 to 100 + SafeWriteBatch(100, { + 0x492120, 0x49212A, // UpdateThing_ + 0x493787, 0x493790 // RestoreSettings_ + }); + SafeWrite8(0x519B82, 0x59); // 100.0 + if (!HRP::Setting::IsEnabled()) { // Corrects the height of the black background for death screen subtitles if (!HRP::Setting::ExternalEnabled()) SafeWrite32(0x48134D, 38 - (640 * 3)); // main_death_scene_ (shift y-offset 2px up, w/o HRP) diff --git a/sfall/Modules/MiscPatches.h b/sfall/Modules/MiscPatches.h index 3ff9264ae..bd3d2c472 100644 --- a/sfall/Modules/MiscPatches.h +++ b/sfall/Modules/MiscPatches.h @@ -33,4 +33,3 @@ class MiscPatches : public Module { }; } - diff --git a/sfall/Modules/Movies.h b/sfall/Modules/Movies.h index 6a76c5f5b..bb972181a 100644 --- a/sfall/Modules/Movies.h +++ b/sfall/Modules/Movies.h @@ -27,7 +27,6 @@ class Movies : public Module { public: const char* name() { return "Movies"; } void init(); - //void exit() override; static bool DirectShowMovies(); }; diff --git a/sfall/Modules/PartyControl.cpp b/sfall/Modules/PartyControl.cpp index 858da15bd..ab1ba203e 100644 --- a/sfall/Modules/PartyControl.cpp +++ b/sfall/Modules/PartyControl.cpp @@ -19,7 +19,6 @@ #include "..\main.h" #include "..\FalloutEngine\Fallout2.h" #include "..\Translate.h" -#include "..\Utils.h" #include "Drugs.h" #include "HookScripts.h" @@ -27,7 +26,6 @@ #include "ScriptExtender.h" //#include "Objects.h" -#include "..\Game\inventory.h" #include "..\Game\objects.h" #include "..\Game\tilemap.h" @@ -43,7 +41,6 @@ static bool isControllingNPC = false; static char skipCounterAnim; static int delayedExperience; -static bool switchHandHookInjected = false; struct WeaponStateSlot { long npcID; @@ -79,8 +76,8 @@ static struct DudeState { struct PartySneakWorking { fo::GameObject* object; - unsigned long sneak_queue_time; - long sneak_working; + DWORD sneak_queue_time; + DWORD sneak_working; }; static std::vector partySneakWorking; @@ -183,9 +180,9 @@ static void SetCurrentDude(fo::GameObject* npc) { // copy existing party member perks or reset list for non-party member NPC long isPartyMember = fo::util::IsPartyMemberByPid(npc->protoId); if (isPartyMember) { - std::memcpy(fo::var::perkLevelDataList, fo::var::perkLevelDataList[isPartyMember - 1].perkData, sizeof(DWORD) * fo::Perk::PERK_count); + std::memcpy(fo::var::perkLevelDataList, fo::var::perkLevelDataList[isPartyMember - 1].perkData, sizeof(DWORD) * fo::PERK_count); } else { - std::memset(fo::var::perkLevelDataList, 0, sizeof(DWORD) * fo::Perk::PERK_count); + std::memset(fo::var::perkLevelDataList, 0, sizeof(DWORD) * fo::PERK_count); } // change level @@ -539,6 +536,7 @@ static void NPCWeaponTweak() { } void PartyControl::SwitchToCritter(fo::GameObject* critter) { + static bool onlyOnce = false; if (skipCounterAnim == 2 && critter && critter == realDude.obj_dude) { skipCounterAnim--; SafeWrite8(0x422BDE, 1); // restore @@ -579,9 +577,8 @@ void PartyControl::SwitchToCritter(fo::GameObject* critter) { } SetCurrentDude(critter); - if (!switchHandHookInjected) { - switchHandHookInjected = true; - //if (!HookScripts::IsInjectHook(HOOK_INVENTORYMOVE)) Inject_SwitchHandHook(); + if (!onlyOnce) { + onlyOnce = true; ScriptExtender::OnMapExit() += []() { if (!partySneakWorking.empty()) { @@ -698,7 +695,7 @@ static void __fastcall action_attack_to(long unused, fo::GameObject* partyMember fo::GameObject* targetObject = nullptr; fo::GameObject* validTarget = nullptr; - long outlineColor; // backup color + long outlineColor = 0; // backup color fo::BoundRect rect; partyOrderPickTargetLoop = true; @@ -709,10 +706,10 @@ static void __fastcall action_attack_to(long unused, fo::GameObject* partyMember targetObject->outline = outlineColor; fo::func::obj_bound(targetObject, &rect); if (!outlineColor) { - rect.x--; - rect.y--; - rect.offx += 2; - rect.offy += 2; + rect.x--; + rect.y--; + rect.offx += 2; + rect.offy += 2; } fo::func::tile_refresh_rect(&rect, fo::var::map_elevation); targetObject = validTarget = nullptr; @@ -759,7 +756,7 @@ static void __fastcall action_attack_to(long unused, fo::GameObject* partyMember break; default: // biped long max = partyOrderAttackMsg.size() - 1; - long rnd = (max > 2) ? GetRandom(2, max) : 2; + long rnd = (max > 2) ? fo::func::roll_random(2, max) : 2; message = partyOrderAttackMsg[rnd].c_str(); } fo::util::PrintFloatText(partyMember, message, cap->color, cap->outline_color, cap->font); @@ -825,6 +822,9 @@ static void __declspec(naked) combat_ai_hook_target() { } void PartyControl::OrderAttackPatch() { + static bool orderAttackPatch = false; + if (orderAttackPatch) return; + MakeCall(0x44C4A7, gmouse_handle_event_hack, 2); HookCall(0x44C75F, gmouse_handle_event_hook); HookCall(0x44C69A, gmouse_handle_event_hook_restore); @@ -834,6 +834,7 @@ void PartyControl::OrderAttackPatch() { LoadGameHook::OnCombatEnd() += []() { partyOrderAttackTarget.clear(); }; + orderAttackPatch = true; } static void NpcAutoLevelPatch() { @@ -869,16 +870,16 @@ void PartyControl::init() { // Display party member's current level & AC & addict flag if (IniReader::GetConfigInt("Misc", "PartyMemberExtraInfo", 0)) { - dlogr("Applying display NPC extra info patch.", DL_INIT); + dlogr("Applying display extra info patch for party members.", DL_INIT); HookCall(0x44926F, gdControlUpdateInfo_hook); Translate::Get("sfall", "PartyLvlMsg", "Lvl:", levelMsg, 12); Translate::Get("sfall", "PartyACMsg", "AC:", armorClassMsg, 12); Translate::Get("sfall", "PartyAddictMsg", "Addict", addictMsg, 16); } - partyOrderAttackMsg.push_back(Translate::Get("sfall", "PartyOrderAttackCreature", "::Growl::", 33)); - partyOrderAttackMsg.push_back(Translate::Get("sfall", "PartyOrderAttackRobot", "::Beep::", 33)); - auto msgs = Translate::GetList("sfall", "PartyOrderAttackHuman", "I'll take care of it.|Okay, I got it.", '|', 512); + partyOrderAttackMsg.push_back(Translate::Get("sfall", "PartyOrderAttackCreature", "::Growl::")); + partyOrderAttackMsg.push_back(Translate::Get("sfall", "PartyOrderAttackRobot", "::Beep::")); + auto msgs = Translate::GetList("sfall", "PartyOrderAttackHuman", "I'll take care of it.|Okay, I got it.", '|'); partyOrderAttackMsg.insert(partyOrderAttackMsg.cend(), msgs.cbegin(), msgs.cend()); } diff --git a/sfall/Modules/Perks.cpp b/sfall/Modules/Perks.cpp index 6eb5c4059..cc8b98019 100644 --- a/sfall/Modules/Perks.cpp +++ b/sfall/Modules/Perks.cpp @@ -1159,7 +1159,7 @@ static void __declspec(naked) item_w_called_shot_hack() { call fo::funcoffs::item_hit_with_; // get pointer to weapon mov edx, ecx; call fo::funcoffs::item_w_subtype_; - cmp eax, THROWING; // is weapon type GUNS or THROWING? + cmp eax, THROWING; // is weapon type RANGED or THROWING? jge checkRange; // yes jmp FastShotTraitFix_End; // continue processing called shot attempt checkRange: @@ -1421,7 +1421,7 @@ void Perks::init() { if (IniReader::GetConfigString("Misc", "PerksFile", "", &perksFile[2], MAX_PATH - 3)) { perksFile[0] = '.'; perksFile[1] = '\\'; - if (GetFileAttributes(perksFile) == INVALID_FILE_ATTRIBUTES) return; + if (GetFileAttributesA(perksFile) == INVALID_FILE_ATTRIBUTES) return; perksEnable = IniReader::GetInt("Perks", "Enable", 1, perksFile); traitsEnable = IniReader::GetInt("Traits", "Enable", 1, perksFile); diff --git a/sfall/Modules/Premade.cpp b/sfall/Modules/Premade.cpp index 499a9cd61..0b0ed9fa9 100644 --- a/sfall/Modules/Premade.cpp +++ b/sfall/Modules/Premade.cpp @@ -92,16 +92,15 @@ static void __declspec(naked) select_display_stats_hook() { jz skip; retn; skip: - mov eax, [esp]; + pop eax; add eax, 94; // offset to next section (0x4A8A60, 0x4A8AC9) - add esp, 4; jmp eax; } } void Premade::init() { - auto premadePaths = IniReader::GetConfigList("misc", "PremadePaths", "", 512); - auto premadeFids = IniReader::GetConfigList("misc", "PremadeFIDs", "", 512); + auto premadePaths = IniReader::GetConfigList("misc", "PremadePaths", ""); + auto premadeFids = IniReader::GetConfigList("misc", "PremadeFIDs", ""); if (!premadePaths.empty() && !premadeFids.empty()) { dlogr("Applying premade characters patch.", DL_INIT); int count = min(premadePaths.size(), premadeFids.size()); diff --git a/sfall/Modules/QuestList.cpp b/sfall/Modules/QuestList.cpp index 4ebba91ee..4b24d85d0 100644 --- a/sfall/Modules/QuestList.cpp +++ b/sfall/Modules/QuestList.cpp @@ -482,7 +482,7 @@ static void __declspec(naked) quest_init_hook() { } } -void QuestListPatch() { +static void QuestListPatch() { MakeCall(0x4974E4, StartPipboy_hack); MakeCall(0x497173, pipboy_hack_action, 1); diff --git a/sfall/Modules/Reputations.cpp b/sfall/Modules/Reputations.cpp index e5c62b9bb..01505c93f 100644 --- a/sfall/Modules/Reputations.cpp +++ b/sfall/Modules/Reputations.cpp @@ -37,7 +37,7 @@ struct CityRep { static CityRep* repList = nullptr; void Reputations::init() { - auto cityRepList = IniReader::GetConfigList("Misc", "CityRepsList", "", 512); + auto cityRepList = IniReader::GetConfigList("Misc", "CityRepsList", ""); size_t count = cityRepList.size(); if (count) { repList = new CityRep[count]; diff --git a/sfall/Modules/ScriptExtender.cpp b/sfall/Modules/ScriptExtender.cpp index 66286dd85..a6ae6f7fd 100644 --- a/sfall/Modules/ScriptExtender.cpp +++ b/sfall/Modules/ScriptExtender.cpp @@ -932,12 +932,12 @@ void ScriptExtender::init() { OnInputLoop() += RunGlobalScriptsOnInput; Worldmap::OnWorldmapLoop() += RunGlobalScriptsOnWorldMap; - globalScriptPathList = IniReader::GetConfigList("Scripts", "GlobalScriptPaths", "scripts\\gl*.int", 255); - for (unsigned int i = 0; i < globalScriptPathList.size(); i++) { + globalScriptPathList = IniReader::GetConfigList("Scripts", "GlobalScriptPaths", "scripts\\gl*.int"); + for (size_t i = 0; i < globalScriptPathList.size(); i++) { ToLowerCase(globalScriptPathList[i]); } - iniConfigFolder = IniReader::GetConfigString("Scripts", "IniConfigFolder", "", 64); + iniConfigFolder = IniReader::GetConfigString("Scripts", "IniConfigFolder", ""); size_t len = iniConfigFolder.length(); if (len) { char c = iniConfigFolder[len - 1]; diff --git a/sfall/Modules/ScriptShaders.cpp b/sfall/Modules/ScriptShaders.cpp index 59a8ef654..f049c795e 100644 --- a/sfall/Modules/ScriptShaders.cpp +++ b/sfall/Modules/ScriptShaders.cpp @@ -221,7 +221,7 @@ void ScriptShaders::Release() { void ScriptShaders::init() { if (Graphics::mode >= 4) { - for each (const auto& shaderFile in IniReader::GetConfigList("Graphics", "GlobalShaderFile", "", 1024)) { + for each (const auto& shaderFile in IniReader::GetConfigList("Graphics", "GlobalShaderFile", "")) { if (shaderFile.length() > 3) gShaderFiles.push_back(GlobalShader(shaderFile)); } globalShadersActive = !gShaderFiles.empty(); diff --git a/sfall/Modules/ScriptShaders.h b/sfall/Modules/ScriptShaders.h index 3961ca647..b35015b1a 100644 --- a/sfall/Modules/ScriptShaders.h +++ b/sfall/Modules/ScriptShaders.h @@ -28,7 +28,6 @@ class ScriptShaders : public Module { public: const char* name() { return "ScriptShaders"; } void init(); - //void exit() override; static size_t Count(); diff --git a/sfall/Modules/Scripting/Arrays.cpp b/sfall/Modules/Scripting/Arrays.cpp index 272ebcf11..72b16c797 100644 --- a/sfall/Modules/Scripting/Arrays.cpp +++ b/sfall/Modules/Scripting/Arrays.cpp @@ -81,6 +81,11 @@ void sArrayElement::setByType( DWORD val, DataType dataType ) } } +void sArrayElement::set(const ScriptValue& val) +{ + setByType(val.rawValue(), val.type()); +} + void sArrayElement::set( long val ) { clearData(); @@ -509,7 +514,7 @@ ScriptValue GetArray(DWORD id, const ScriptValue& key) { return ScriptValue(0); } -void setArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset) { +void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset) { sArrayVar &arr = arrays[id]; if (arr.isAssoc()) { sArrayElement sEl(key.rawValue(), key.type()); @@ -542,23 +547,19 @@ void setArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool all // add pair el = arr.val.size(); arr.val.resize(el + 2); - arr.val[el].setByType(key.rawValue(), key.type()); // copy data + arr.val[el].set(key); // copy data arr.keyHash[arr.val[el]] = el; } - arr.val[el + 1].setByType(val.rawValue(), val.type()); + arr.val[el + 1].set(val); } } else if (key.isInt()) { // only update normal array if key is an integer and within array size size_t index = key.rawValue(); if (arr.val.size() > index) { - arr.val[index].setByType(val.rawValue(), val.type()); + arr.val[index].set(val); } } } -void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset) { - if (ArrayExists(id)) setArray(id, key, val, allowUnset); -} - int LenArray(DWORD id) { array_itr it = arrays.find(id); return (it != arrays.end()) ? it->second.size() : -1; @@ -735,7 +736,7 @@ void SaveArray(const ScriptValue& key, DWORD id) { } } // make array "saved" - itArray->second.key.setByType(key.rawValue(), key.type()); + itArray->second.key.set(key); savedArrays.emplace(itArray->second.key, id); // savedArrays[itArray->second.key] = id; } else { // key of int(0) is used to "unsave" array without destroying it savedArrays.erase(itArray->second.key); @@ -762,7 +763,7 @@ long StackArray(const ScriptValue& key, const ScriptValue& val) { if (size >= ARRAY_MAX_SIZE) return 0; if (key.rawValue() >= size) arrays[stackArrayId].val.resize(size + 1); } - setArray(stackArrayId, key, val, false); + SetArray(stackArrayId, key, val, false); return 0; } diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index 147228fa0..1fc0efdb5 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -29,7 +29,7 @@ namespace sfall namespace script { -#define ARRAY_MAX_STRING (255) // maximum length of string to be stored as array key or value +#define ARRAY_MAX_STRING (1024) // maximum length of string to be stored as array key or value (including null terminator) #define ARRAY_MAX_SIZE (100000) // maximum number of array elements, // so total maximum memory/disk footprint of one array is: 16 + (ARRAY_MAX_STRING + 8) * ARRAY_MAX_SIZE @@ -68,6 +68,7 @@ class sArrayElement setByType(el.intVal, el.type); } + void set(const ScriptValue& val); void set(long val); void set(float val); void set(const char* val, int _len = -1); @@ -202,11 +203,8 @@ ScriptValue GetArrayKey(DWORD id, int index); // get array element by index (list) or key (map) ScriptValue GetArray(DWORD id, const ScriptValue& key); -// set array element by index or key (with checking the existence of the array ID) -void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset); - // set array element by index or key -void setArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset); +void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset); // number of elements in list or pairs in map int LenArray(DWORD id); diff --git a/sfall/Modules/Scripting/Handlers/Arrays.cpp b/sfall/Modules/Scripting/Handlers/Arrays.cpp index e8a362fbb..9735469bd 100644 --- a/sfall/Modules/Scripting/Handlers/Arrays.cpp +++ b/sfall/Modules/Scripting/Handlers/Arrays.cpp @@ -34,8 +34,11 @@ void op_create_array(OpcodeContext& ctx) { } void op_set_array(OpcodeContext& ctx) { + auto arrayId = ctx.arg(0).rawValue(); + if (!ArrayExists(arrayId)) return; + SetArray( - ctx.arg(0).rawValue(), + arrayId, ctx.arg(1), ctx.arg(2), true diff --git a/sfall/Modules/Scripting/Handlers/Combat.cpp b/sfall/Modules/Scripting/Handlers/Combat.cpp index d9e281dc3..e556eb705 100644 --- a/sfall/Modules/Scripting/Handlers/Combat.cpp +++ b/sfall/Modules/Scripting/Handlers/Combat.cpp @@ -19,6 +19,7 @@ #include "..\..\..\FalloutEngine\AsmMacros.h" #include "..\..\..\FalloutEngine\Fallout2.h" #include "..\..\AI.h" +#include "..\..\BurstMods.h" #include "..\..\Combat.h" #include "..\..\KillCounter.h" @@ -235,11 +236,48 @@ void mf_attack_is_aimed(OpcodeContext& ctx) { void mf_combat_data(OpcodeContext& ctx) { fo::ComputeAttackResult* ctd = nullptr; - if (fo::var::combat_state & 1) { + if (fo::var::combat_state & fo::CombatStateFlag::InCombat) { ctd = &fo::var::main_ctd; } ctx.setReturn((DWORD)ctd, DataType::INT); } +void mf_set_spray_settings(OpcodeContext& ctx) { + long centerMult = ctx.arg(0).rawValue(), + centerDiv = ctx.arg(1).rawValue(), + targetMult = ctx.arg(2).rawValue(), + targetDiv = ctx.arg(3).rawValue(); + + if (centerDiv < 1) centerDiv = 1; + if (centerMult < 1) { + centerMult = 1; + } else if (centerMult > centerDiv) { + centerMult = centerDiv; + ctx.printOpcodeError("%s() - Warning: centerMult value is capped at centerDiv.", ctx.getMetaruleName()); + } + if (targetDiv < 1) targetDiv = 1; + if (targetMult < 1) { + targetMult = 1; + } else if (targetMult > targetDiv) { + targetMult = targetDiv; + ctx.printOpcodeError("%s() - Warning: targetMult value is capped at targetDiv.", ctx.getMetaruleName()); + } + BurstMods::SetComputeSpraySettings(centerMult, centerDiv, targetMult, targetDiv); +} + +void mf_get_combat_free_move(OpcodeContext& ctx) { + ctx.setReturn(fo::var::combat_free_move); +} + +void mf_set_combat_free_move(OpcodeContext& ctx) { + long value = ctx.arg(0).rawValue(); + if (value < 0) value = 0; + + fo::var::combat_free_move = value; + if (fo::var::main_ctd.attacker == fo::var::obj_dude) { + fo::func::intface_update_move_points(fo::var::obj_dude->critter.movePoints, fo::var::combat_free_move); + } +} + } } diff --git a/sfall/Modules/Scripting/Handlers/Combat.h b/sfall/Modules/Scripting/Handlers/Combat.h index e480457cf..7ed5aae51 100644 --- a/sfall/Modules/Scripting/Handlers/Combat.h +++ b/sfall/Modules/Scripting/Handlers/Combat.h @@ -56,5 +56,11 @@ void mf_attack_is_aimed(OpcodeContext&); void mf_combat_data(OpcodeContext&); +void mf_set_spray_settings(OpcodeContext&); + +void mf_get_combat_free_move(OpcodeContext&); + +void mf_set_combat_free_move(OpcodeContext&); + } } diff --git a/sfall/Modules/Scripting/Handlers/Core.cpp b/sfall/Modules/Scripting/Handlers/Core.cpp index b710649d1..2488277ca 100644 --- a/sfall/Modules/Scripting/Handlers/Core.cpp +++ b/sfall/Modules/Scripting/Handlers/Core.cpp @@ -105,53 +105,35 @@ void op_get_sfall_global_float(OpcodeContext& ctx) { GetGlobalVar(ctx, DataType::FLOAT); } -void __declspec(naked) op_get_sfall_arg() { - __asm { - mov esi, ecx; - call HookCommon::GetHSArg; - mov edx, eax; - mov eax, ebx; - _RET_VAL_INT; - mov ecx, esi; - retn; - } +void op_get_sfall_arg(OpcodeContext& ctx) { + ctx.setReturn(HookCommon::GetHSArg()); } void mf_get_sfall_arg_at(OpcodeContext& ctx) { - long argVal = 0; long id = ctx.arg(0).rawValue(); if (id >= static_cast(HookCommon::GetHSArgCount()) || id < 0) { ctx.printOpcodeError("%s() - invalid value for argument.", ctx.getMetaruleName()); - } else { - argVal = HookCommon::GetHSArgAt(id); + ctx.setReturn(0); + return; } - ctx.setReturn(argVal); + ctx.setReturn(HookCommon::GetHSArgAt(id)); } void op_get_sfall_args(OpcodeContext& ctx) { DWORD argCount = HookCommon::GetHSArgCount(); DWORD id = CreateTempArray(argCount, 0); - DWORD* args = HookCommon::GetHSArgs(); for (DWORD i = 0; i < argCount; i++) { - arrays[id].val[i].set(*(long*)&args[i]); + arrays[id].val[i].set(HookCommon::GetHSArgAt(i)); } ctx.setReturn(id); } void op_set_sfall_arg(OpcodeContext& ctx) { - HookCommon::SetHSArg(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); + HookCommon::SetHSArg(ctx.arg(0).rawValue(), ctx.arg(1)); } -void __declspec(naked) op_set_sfall_return() { - __asm { - mov esi, ecx; - _GET_ARG_INT(end); - push eax; - call HookCommon::SetHSReturn; -end: - mov ecx, esi; - retn; - } +void op_set_sfall_return(OpcodeContext& ctx) { + HookCommon::SetHSReturn(ctx.arg(0)); } void __declspec(naked) op_game_loaded() { diff --git a/sfall/Modules/Scripting/Handlers/Core.h b/sfall/Modules/Scripting/Handlers/Core.h index 69baeb062..408af9233 100644 --- a/sfall/Modules/Scripting/Handlers/Core.h +++ b/sfall/Modules/Scripting/Handlers/Core.h @@ -41,7 +41,7 @@ void op_get_sfall_global_int(OpcodeContext&); void op_get_sfall_global_float(OpcodeContext&); -void __declspec() op_get_sfall_arg(); +void op_get_sfall_arg(OpcodeContext&); void mf_get_sfall_arg_at(OpcodeContext&); @@ -49,7 +49,7 @@ void op_get_sfall_args(OpcodeContext&); void op_set_sfall_arg(OpcodeContext&); -void __declspec() op_set_sfall_return(); +void op_set_sfall_return(OpcodeContext&); void __declspec() op_game_loaded(); diff --git a/sfall/Modules/Scripting/Handlers/IniFiles.cpp b/sfall/Modules/Scripting/Handlers/IniFiles.cpp new file mode 100644 index 000000000..40e7848e9 --- /dev/null +++ b/sfall/Modules/Scripting/Handlers/IniFiles.cpp @@ -0,0 +1,279 @@ +/* + * sfall + * Copyright (C) 2008-2023 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 "IniFiles.h" + +#include "..\..\..\Config.h" +#include "..\..\..\Utils.h" +#include "..\..\ScriptExtender.h" +#include "..\Arrays.h" + +#include +#include + +namespace sfall +{ +namespace script +{ + +static std::unordered_map ConfigArrayCache; +static std::unordered_map ConfigArrayCacheDat; + +void ResetIniCache() { + ConfigArrayCache.clear(); + ConfigArrayCacheDat.clear(); +} + +static bool IsSpecialIni(const char* str, const char* end) { + const char* pos = strfind(str, &IniReader::instance().getConfigFile()[2]); // TODO test + if (pos && pos < end) return true; + pos = strfind(str, "f2_res.ini"); + if (pos && pos < end) return true; + return false; +} + +static int ParseIniSetting(const char* iniString, const char* &key, char section[], char file[]) { + key = strstr(iniString, "|"); + if (!key) return -1; + + DWORD filelen = (DWORD)key - (DWORD)iniString; + if (ScriptExtender::iniConfigFolder.empty() && filelen >= 64) return -1; + const char* fileEnd = key; + + key = strstr(key + 1, "|"); + if (!key) return -1; + + DWORD seclen = (DWORD)key - ((DWORD)iniString + filelen + 1); + if (seclen > 32) return -1; + + file[0] = '.'; + file[1] = '\\'; + + if (!ScriptExtender::iniConfigFolder.empty() && !IsSpecialIni(iniString, fileEnd)) { + size_t len = ScriptExtender::iniConfigFolder.length(); // limit up to 62 characters + memcpy(&file[2], ScriptExtender::iniConfigFolder.c_str(), len); + memcpy(&file[2 + len], iniString, filelen); // copy path and file + file[2 + len + filelen] = 0; + if (GetFileAttributesA(file) & FILE_ATTRIBUTE_DIRECTORY) goto defRoot; // also file not found + } else { +defRoot: + memcpy(&file[2], iniString, filelen); + file[2 + filelen] = 0; + } + memcpy(section, &iniString[filelen + 1], seclen); + section[seclen] = 0; + + key++; + return 1; +} + +static DWORD GetIniSetting(const char* str, bool isString) { + const char* key; + char section[33], file[128]; + + if (ParseIniSetting(str, key, section, file) < 0) { + return -1; + } + if (isString) { + ScriptExtender::gTextBuffer[0] = 0; + IniReader::GetString(section, key, "", ScriptExtender::gTextBuffer, 1024, file); + return (DWORD)&ScriptExtender::gTextBuffer[0]; + } else { + return IniReader::GetInt(section, key, -1, file); + } +} + +void op_get_ini_setting(OpcodeContext& ctx) { + ctx.setReturn(GetIniSetting(ctx.arg(0).strValue(), false)); +} + +void op_get_ini_string(OpcodeContext& ctx) { + DWORD result = GetIniSetting(ctx.arg(0).strValue(), true); + ctx.setReturn(result, (result != -1) ? DataType::STR : DataType::INT); +} + +void op_modified_ini(OpcodeContext& ctx) { + ctx.setReturn(IniReader::instance().modifiedIni()); +} + +void mf_set_ini_setting(OpcodeContext& ctx) { + const ScriptValue &argVal = ctx.arg(1); + + const char* saveValue; + if (argVal.isInt()) { + _itoa_s(argVal.rawValue(), ScriptExtender::gTextBuffer, 10); + saveValue = ScriptExtender::gTextBuffer; + } else { + saveValue = argVal.strValue(); + } + const char* key; + char section[33], file[128]; + int result = ParseIniSetting(ctx.arg(0).strValue(), key, section, file); + if (result > 0) { + result = IniReader::instance().setString(section, key, saveValue, file); + } + + switch (result) { + case 0: + ctx.printOpcodeError("%s() - value save error.", ctx.getMetaruleName()); + break; + case -1: + ctx.printOpcodeError("%s() - invalid setting argument.", ctx.getMetaruleName()); + break; + default: + return; + } + ctx.setReturn(-1); +} + +// Sanitizes path for db_fopen: +// - Disallows going outside of game folder. +// - Normalizes directory separators. +// - Trims whitespaces. +static std::string GetSanitizedDBPath(const char* pathArg) { + const char* whiteSpaces = " \t\r\n"; + std::string path(pathArg); + std::replace(path.begin(), path.end(), '/', '\\'); // Normalize directory separators. + path.erase(0, path.find_first_not_of(whiteSpaces)); // trim left + if (path[0] == '\\' || + path.find(':') != std::string::npos || + path.find("..") != std::string::npos) return ""; // don't allow absolute paths or going outside of root + + if (path.find(".\\") == 0) path.erase(0, 2); // remove leading ".\" + path.erase(path.find_last_not_of(whiteSpaces) + 1); // trim right + return std::move(path); +} + +static std::string GetIniFilePathFromArg(const ScriptValue& arg) { + const char* pathArg = arg.strValue(); + std::string fileName(".\\"); + if (ScriptExtender::iniConfigFolder.empty()) { + fileName += pathArg; + } else { + fileName += ScriptExtender::iniConfigFolder; + fileName += pathArg; + if (GetFileAttributesA(fileName.c_str()) & FILE_ATTRIBUTE_DIRECTORY) { + auto str = pathArg; + for (size_t i = 2; ; i++, str++) { + //if (*str == '.') str += (str[1] == '.') ? 3 : 2; // skip '.\' or '..\' + fileName[i] = *str; + if (!*str) break; + } + } + } + return std::move(fileName); +} + +void mf_get_ini_sections(OpcodeContext& ctx) { + Config* config = IniReader::instance().getIniConfig(GetIniFilePathFromArg(ctx.arg(0)).c_str()); + if (config == nullptr) { + ctx.setReturn(CreateTempArray(0, 0)); + return; + } + const auto& data = config->data(); + size_t numSections = config->data().size(); + DWORD arrayId = CreateTempArray(numSections, 0); + size_t i = 0; + for (auto sectIt = data.cbegin(); sectIt != data.cend(); ++sectIt) { + arrays[arrayId].val[i].set(sectIt->first.c_str(), sectIt->first.size()); + ++i; + } + ctx.setReturn(arrayId); +} + +static void CopyConfigSectionToArray(DWORD arrayId, const Config::Section& section) { + for (auto valueIt = section.cbegin(); valueIt != section.cend(); ++valueIt) { + SetArray(arrayId, valueIt->first.c_str(), valueIt->second.c_str(), false); + } +} + +void mf_get_ini_section(OpcodeContext& ctx) { + auto sectionName = ctx.arg(1).strValue(); + DWORD arrayId = CreateTempArray(-1, 0); // associative + ctx.setReturn(arrayId); + + Config* config = IniReader::instance().getIniConfig(GetIniFilePathFromArg(ctx.arg(0)).c_str()); + if (config == nullptr) return; // ini file not found + + const auto& data = config->data(); + auto sectIt = data.find(sectionName); + if (sectIt == data.end()) return; // ini section not found + + CopyConfigSectionToArray(arrayId, sectIt->second); +} + +void mf_get_ini_config(OpcodeContext& ctx) { + bool isDb = ctx.arg(1).asBool(); + std::string filePath(isDb + ? GetSanitizedDBPath(ctx.arg(0).strValue()) + : GetIniFilePathFromArg(ctx.arg(0))); + + if (filePath.size() == 0) { + ctx.printOpcodeError("%s() - invalid config file path: %s", ctx.getMetaruleName(), ctx.arg(0).strValue()); + ctx.setReturn(0); + return; + } + + // Check if array exists in either cache. + auto& cache = isDb ? ConfigArrayCacheDat : ConfigArrayCache; + auto cacheHit = cache.find(filePath); + if (cacheHit != cache.end()) { + if (ArrayExists(cacheHit->second)) { + // Previously loaded array still exists, so return it. + ctx.setReturn(cacheHit->second); + return; + } + // Array was deleted. Remove it from cache and proceed with loading. + cache.erase(cacheHit); + } + + // Try to read INI config from DAT database. + Config* config; + std::unique_ptr configUniq; + if (isDb) { + configUniq = std::make_unique(); + if (!configUniq->read(filePath.c_str(), isDb)) { + ctx.printOpcodeError("%s() - cannot read config file from DAT: %s", ctx.getMetaruleName(), filePath.c_str()); + ctx.setReturn(0); + return; + } + config = configUniq.get(); + } else { + // Request config from IniReader (to take advantage of it's cache). + config = IniReader::instance().getIniConfig(filePath.c_str()); + if (config == nullptr) { + ctx.printOpcodeError("%s() - cannot read config file: %s", ctx.getMetaruleName(), filePath.c_str()); + ctx.setReturn(0); + return; + } + } + // Copy data to new Sfall Array. + DWORD arrayId = CreateArray(-1, 0); + const auto& data = config->data(); + for (auto sectIt = data.cbegin(); sectIt != data.cend(); ++sectIt) { + DWORD subArrayId = CreateArray(-1, 0); + CopyConfigSectionToArray(subArrayId, sectIt->second); + SetArray(arrayId, sectIt->first.c_str(), subArrayId, false); + } + // Save new array ID to cache and return it. + cache.emplace(std::move(filePath), arrayId); + ctx.setReturn(arrayId); +} + +} +} diff --git a/sfall/Modules/Scripting/Handlers/IniFiles.h b/sfall/Modules/Scripting/Handlers/IniFiles.h new file mode 100644 index 000000000..2a066cb0d --- /dev/null +++ b/sfall/Modules/Scripting/Handlers/IniFiles.h @@ -0,0 +1,45 @@ +/* + * sfall + * Copyright (C) 2008-2023 The sfall team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "..\OpcodeContext.h" + +namespace sfall +{ +namespace script +{ + +void op_get_ini_setting(OpcodeContext&); + +void op_get_ini_string(OpcodeContext&); + +void op_modified_ini(OpcodeContext&); + +void mf_set_ini_setting(OpcodeContext&); + +void mf_get_ini_sections(OpcodeContext&); + +void mf_get_ini_section(OpcodeContext&); + +void mf_get_ini_config(OpcodeContext&); + +void ResetIniCache(); + +} +} diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 7321dcc5d..cc8e4a756 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -709,10 +709,10 @@ void mf_get_window_attribute(OpcodeContext& ctx) { switch (ctx.arg(1).rawValue()) { case -1: // rectangle map.left map.top map.right map.bottom result = CreateTempArray(-1, 0); // associative - setArray(result, ScriptValue("left"), ScriptValue(win->rect.x), false); - setArray(result, ScriptValue("top"), ScriptValue(win->rect.y), false); - setArray(result, ScriptValue("right"), ScriptValue(win->rect.offx), false); - setArray(result, ScriptValue("bottom"), ScriptValue(win->rect.offy), false); + SetArray(result, ScriptValue("left"), ScriptValue(win->rect.x), false); + SetArray(result, ScriptValue("top"), ScriptValue(win->rect.y), false); + SetArray(result, ScriptValue("right"), ScriptValue(win->rect.offx), false); + SetArray(result, ScriptValue("bottom"), ScriptValue(win->rect.offy), false); break; case 0: // check if window exists result = 1; diff --git a/sfall/Modules/Scripting/Handlers/Inventory.cpp b/sfall/Modules/Scripting/Handlers/Inventory.cpp index b71f475a2..c4327c3cd 100644 --- a/sfall/Modules/Scripting/Handlers/Inventory.cpp +++ b/sfall/Modules/Scripting/Handlers/Inventory.cpp @@ -96,7 +96,7 @@ void mf_critter_inven_obj2(OpcodeContext& ctx) { ctx.setReturn(critter->invenSize); break; default: - ctx.printOpcodeError("%s() - invalid type.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid type number.", ctx.getMetaruleName()); } } diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 8034c37e4..f1000b1ae 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -23,6 +23,7 @@ #include "Anims.h" #include "Combat.h" #include "Core.h" +#include "IniFiles.h" #include "Interface.h" #include "Inventory.h" #include "Math.h" @@ -35,6 +36,10 @@ #include "Metarule.h" +#ifndef NDEBUG +#include +#endif + namespace sfall { namespace script @@ -60,7 +65,7 @@ static MetaruleTableType metaruleTable; { name, handler, minArgs, maxArgs, error, {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 (max 6) + - minArgs/maxArgs - minimum and maximum number of arguments allowed for this function (max 8) - returned error value for argument validation, - arg1, arg2, ... - argument types for automatic validation */ @@ -83,9 +88,11 @@ static const SfallMetarule metarules[] = { {"exec_map_update_scripts", mf_exec_map_update_scripts, 0, 0}, {"floor2", mf_floor2, 1, 1, 0, {ARG_NUMBER}}, {"get_can_rest_on_map", mf_get_rest_on_map, 2, 2, -1, {ARG_INT, ARG_INT}}, + {"get_combat_free_move", mf_get_combat_free_move, 0, 0}, {"get_current_inven_size", mf_get_current_inven_size, 1, 1, 0, {ARG_OBJECT}}, {"get_cursor_mode", mf_get_cursor_mode, 0, 0}, {"get_flags", mf_get_flags, 1, 1, 0, {ARG_OBJECT}}, + {"get_ini_config", mf_get_ini_config, 2, 2, 0, {ARG_STRING, ARG_INT}}, {"get_ini_section", mf_get_ini_section, 2, 2, -1, {ARG_STRING, ARG_STRING}}, {"get_ini_sections", mf_get_ini_sections, 1, 1, -1, {ARG_STRING}}, {"get_inven_ap_cost", mf_get_inven_ap_cost, 0, 0}, @@ -125,8 +132,10 @@ static const SfallMetarule metarules[] = { {"outlined_object", mf_outlined_object, 0, 0}, {"real_dude_obj", mf_real_dude_obj, 0, 0}, {"remove_timer_event", mf_remove_timer_event, 0, 1, -1, {ARG_INT}}, + {"set_spray_settings", mf_set_spray_settings, 4, 4, -1, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {"set_can_rest_on_map", mf_set_rest_on_map, 3, 3, -1, {ARG_INT, ARG_INT, ARG_INT}}, {"set_car_intface_art", mf_set_car_intface_art, 1, 1, -1, {ARG_INT}}, + {"set_combat_free_move", mf_set_combat_free_move, 1, 1, -1, {ARG_INT}}, {"set_cursor_mode", mf_set_cursor_mode, 1, 1, -1, {ARG_INT}}, {"set_drugs_data", mf_set_drugs_data, 3, 3, -1, {ARG_INT, ARG_INT, ARG_INT}}, {"set_dude_obj", mf_set_dude_obj, 1, 1, -1, {ARG_INT}}, @@ -151,7 +160,8 @@ static const SfallMetarule metarules[] = { {"show_window", mf_show_window, 0, 1, -1, {ARG_STRING}}, {"spatial_radius", mf_spatial_radius, 1, 1, 0, {ARG_OBJECT}}, {"string_compare", mf_string_compare, 2, 3, 0, {ARG_STRING, ARG_STRING, ARG_INT}}, - {"string_format", mf_string_format, 2, 5, 0, {ARG_STRING, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY}}, + {"string_find", mf_string_find, 2, 3, -1, {ARG_STRING, ARG_STRING, ARG_INT}}, + {"string_format", mf_string_format, 2, 8, 0, {ARG_STRING, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY, ARG_ANY}}, {"string_to_case", mf_string_to_case, 2, 2, -1, {ARG_STRING, ARG_INT}}, {"tile_by_position", mf_tile_by_position, 2, 2, -1, {ARG_INT, ARG_INT}}, {"tile_refresh_display", mf_tile_refresh_display, 0, 0}, diff --git a/sfall/Modules/Scripting/Handlers/Metarule.h b/sfall/Modules/Scripting/Handlers/Metarule.h index b115dd5b4..e394e33ec 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.h +++ b/sfall/Modules/Scripting/Handlers/Metarule.h @@ -19,7 +19,6 @@ #pragma once #include -#include #include #include "..\..\..\main.h" diff --git a/sfall/Modules/Scripting/Handlers/Misc.cpp b/sfall/Modules/Scripting/Handlers/Misc.cpp index d40c4f6c6..9b80251da 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.cpp +++ b/sfall/Modules/Scripting/Handlers/Misc.cpp @@ -112,74 +112,6 @@ void __declspec(naked) op_set_eax_environment() { } } -static bool IsSpecialIni(const char* str, const char* end) { - const char* pos = strfind(str, &IniReader::GetConfigFile()[2]); // TODO test - if (pos && pos < end) return true; - pos = strfind(str, "f2_res.ini"); - if (pos && pos < end) return true; - return false; -} - -static int ParseIniSetting(const char* iniString, const char* &key, char section[], char file[]) { - key = strstr(iniString, "|"); - if (!key) return -1; - - DWORD filelen = (DWORD)key - (DWORD)iniString; - if (ScriptExtender::iniConfigFolder.empty() && filelen >= 64) return -1; - const char* fileEnd = key; - - key = strstr(key + 1, "|"); - if (!key) return -1; - - DWORD seclen = (DWORD)key - ((DWORD)iniString + filelen + 1); - if (seclen > 32) return -1; - - file[0] = '.'; - file[1] = '\\'; - - if (!ScriptExtender::iniConfigFolder.empty() && !IsSpecialIni(iniString, fileEnd)) { - size_t len = ScriptExtender::iniConfigFolder.length(); // limit up to 62 characters - memcpy(&file[2], ScriptExtender::iniConfigFolder.c_str(), len); - memcpy(&file[2 + len], iniString, filelen); // copy path and file - file[2 + len + filelen] = 0; - if (GetFileAttributesA(file) & FILE_ATTRIBUTE_DIRECTORY) goto defRoot; // also file not found - } else { -defRoot: - memcpy(&file[2], iniString, filelen); - file[2 + filelen] = 0; - } - memcpy(section, &iniString[filelen + 1], seclen); - section[seclen] = 0; - - key++; - return 1; -} - -static DWORD GetIniSetting(const char* str, bool isString) { - const char* key; - char section[33], file[128]; - - if (ParseIniSetting(str, key, section, file) < 0) { - return -1; - } - if (isString) { - ScriptExtender::gTextBuffer[0] = 0; - IniReader::GetString(section, key, "", ScriptExtender::gTextBuffer, 256, file); - return (DWORD)&ScriptExtender::gTextBuffer[0]; - } else { - return IniReader::GetInt(section, key, -1, file); - } -} - -void op_get_ini_setting(OpcodeContext& ctx) { - ctx.setReturn(GetIniSetting(ctx.arg(0).strValue(), false)); -} - -void op_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() { __asm { mov esi, ecx; @@ -331,38 +263,35 @@ void __declspec(naked) op_stop_sfall_sound() { } } -// TODO: It seems that this function does not work... -void __declspec(naked) op_get_tile_fid() { - __asm { - push ecx; - _GET_ARG_INT(fail); // get tile value - mov esi, ebx; // keep script - sub esp, 8; // x/y buf - lea edx, [esp]; - lea ebx, [esp + 4]; - call fo::funcoffs::tile_coord_; - pop eax; // x - pop edx; // y - call fo::funcoffs::square_num_; - mov edx, ds:[FO_VAR_square]; - movzx edx, word ptr ds:[edx + eax * 4]; - mov ebx, esi; // script -end: - mov eax, ebx; - _RET_VAL_INT; - pop ecx; - retn; -fail: - xor edx, edx; // return 0 - jmp end; +void op_get_tile_fid(OpcodeContext& ctx) { + long tileX, tileY, squareNum, squareData, result, + tileAndElev = ctx.arg(0).rawValue(), + tileNum = tileAndElev & 0xFFFFFF, + elevation = (tileAndElev >> 24) & 0x0F, + mode = tileAndElev >> 28; + + if (tileNum >= 40000 || elevation > 2) { + ctx.printOpcodeError("%s() - invalid tile data argument.", ctx.getMetaruleName()); + ctx.setReturn(0); + return; } -} -void __declspec(naked) op_modified_ini() { - __asm { - mov edx, IniReader::modifiedIni; - _J_RET_VAL_TYPE(VAR_TYPE_INT); + fo::func::tile_coord(tileNum, &tileX, &tileY); + squareNum = fo::func::square_num(tileX, tileY, elevation); + squareData = fo::var::square[elevation][squareNum]; + switch (mode) { + case 1: + result = (squareData >> 16) & 0x3FFF; // roof + break; + case 2: + result = squareData; // raw data + break; + default: + // Vanilla uses 12 bits for Tile FID, which means 4096 possible values, the mask was 0x0FFF + // BUT sfall's FRM Limit patch extended it to 14 bits, so we need to use mask 0x3FFF + result = squareData & 0x3FFF; // this is how opcode worked up to 4.3.8 } + ctx.setReturn(result); } void __declspec(naked) op_mark_movie_played() { @@ -415,102 +344,6 @@ void mf_exec_map_update_scripts(OpcodeContext& ctx) { __asm call fo::funcoffs::scr_exec_map_update_scripts_ } -void mf_set_ini_setting(OpcodeContext& ctx) { - const ScriptValue &argVal = ctx.arg(1); - - const char* saveValue; - if (argVal.isInt()) { - _itoa_s(argVal.rawValue(), ScriptExtender::gTextBuffer, 10); - saveValue = ScriptExtender::gTextBuffer; - } else { - saveValue = argVal.strValue(); - } - const char* key; - char section[33], file[128]; - int result = ParseIniSetting(ctx.arg(0).strValue(), key, section, file); - if (result > 0) { - result = WritePrivateProfileStringA(section, key, saveValue, file); - } - - switch (result) { - case 0: - ctx.printOpcodeError("%s() - value save error.", ctx.getMetaruleName()); - break; - case -1: - ctx.printOpcodeError("%s() - invalid setting argument.", ctx.getMetaruleName()); - break; - default: - return; - } - ctx.setReturn(-1); -} - -static std::string GetIniFilePath(const ScriptValue &arg) { - std::string fileName(".\\"); - if (ScriptExtender::iniConfigFolder.empty()) { - fileName += arg.strValue(); - } else { - fileName += ScriptExtender::iniConfigFolder; - fileName += arg.strValue(); - if (GetFileAttributesA(fileName.c_str()) & FILE_ATTRIBUTE_DIRECTORY) { - auto str = arg.strValue(); - for (size_t i = 2; ; i++, str++) { - //if (*str == '.') str += (str[1] == '.') ? 3 : 2; // skip '.\' or '..\' - fileName[i] = *str; - if (!*str) break; - } - } - } - return fileName; -} - -void mf_get_ini_sections(OpcodeContext& ctx) { - if (!GetPrivateProfileSectionNamesA(ScriptExtender::gTextBuffer, ScriptExtender::TextBufferSize(), GetIniFilePath(ctx.arg(0)).c_str())) { - ctx.setReturn(CreateTempArray(0, 0)); - return; - } - std::vector sections; - char* section = ScriptExtender::gTextBuffer; - while (*section != 0) { - sections.push_back(section); // position - section += std::strlen(section) + 1; - } - size_t sz = sections.size(); - int arrayId = CreateTempArray(sz, 0); - auto& arr = arrays[arrayId]; - - 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); // copy string from buffer - } - ctx.setReturn(arrayId); -} - -void mf_get_ini_section(OpcodeContext& ctx) { - auto section = ctx.arg(1).strValue(); - int arrayId = CreateTempArray(-1, 0); // associative - - if (GetPrivateProfileSectionA(section, ScriptExtender::gTextBuffer, ScriptExtender::TextBufferSize(), GetIniFilePath(ctx.arg(0)).c_str())) { - auto& arr = arrays[arrayId]; - char *key = ScriptExtender::gTextBuffer, *val = nullptr; - while (*key != 0) { - char* val = std::strpbrk(key, "="); - if (val != nullptr) { - *val = '\0'; - val += 1; - - setArray(arrayId, ScriptValue(key), ScriptValue(val), false); - - key = val + std::strlen(val) + 1; - } else { - key += std::strlen(key) + 1; - } - } - } - ctx.setReturn(arrayId); -} - void mf_set_quest_failure_value(OpcodeContext& ctx) { QuestList::AddQuestFailureValue(ctx.arg(0).rawValue(), ctx.arg(1).rawValue()); } diff --git a/sfall/Modules/Scripting/Handlers/Misc.h b/sfall/Modules/Scripting/Handlers/Misc.h index f6cbc6d6a..010f8edfd 100644 --- a/sfall/Modules/Scripting/Handlers/Misc.h +++ b/sfall/Modules/Scripting/Handlers/Misc.h @@ -49,10 +49,6 @@ void __declspec() op_eax_available(); void __declspec() op_set_eax_environment(); -void op_get_ini_setting(OpcodeContext&); - -void op_get_ini_string(OpcodeContext&); - void __declspec() op_get_uptime(); void __declspec() op_set_car_current_town(); @@ -82,9 +78,7 @@ void op_play_sfall_sound(OpcodeContext&); void __declspec() op_stop_sfall_sound(); -void __declspec() op_get_tile_fid(); - -void __declspec() op_modified_ini(); +void op_get_tile_fid(OpcodeContext&); void __declspec() op_mark_movie_played(); @@ -98,12 +92,6 @@ void op_tile_light(OpcodeContext&); void mf_exec_map_update_scripts(OpcodeContext&); -void mf_set_ini_setting(OpcodeContext&); - -void mf_get_ini_sections(OpcodeContext&); - -void mf_get_ini_section(OpcodeContext&); - void mf_set_quest_failure_value(OpcodeContext&); void mf_set_scr_name(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Objects.cpp b/sfall/Modules/Scripting/Handlers/Objects.cpp index 40a61a67d..bc3177498 100644 --- a/sfall/Modules/Scripting/Handlers/Objects.cpp +++ b/sfall/Modules/Scripting/Handlers/Objects.cpp @@ -102,12 +102,7 @@ void op_get_npc_level(OpcodeContext& ctx) { auto members = fo::var::partyMemberList; for (DWORD i = 0; i < fo::var::partyMemberCount; i++) { if (!findPid) { - __asm { - mov eax, members; - mov eax, [eax]; - call fo::funcoffs::critter_name_; - mov critterName, eax; - } + critterName = fo::func::critter_name(members[i].object); if (!_stricmp(name, critterName)) { // found npc pid = members[i].object->protoId; break; @@ -157,7 +152,7 @@ void op_set_script(OpcodeContext& ctx) { long scriptIndex = valArg & ~0xF0000000; if (scriptIndex == 0 || valArg > 0x8FFFFFFF) { // negative values are not allowed - ctx.printOpcodeError("%s() - the script index number is incorrect.", ctx.getOpcodeName()); + ctx.printOpcodeError("%s() - invalid script index number.", ctx.getOpcodeName()); return; } scriptIndex--; @@ -198,7 +193,10 @@ void op_create_spatial(OpcodeContext& ctx) { // this will load appropriate script program and link it to the script instance we just created: exec_script_proc(scriptId, start); - ctx.setReturn(fo::func::scr_find_obj_from_program(scriptPtr->program)); + fo::GameObject* obj = fo::func::scr_find_obj_from_program(scriptPtr->program); + // set script index because scr_find_obj_from_program() doesn't do it when creating a hidden "spatial" object + obj->scriptIndex = scriptIndex - 1; + ctx.setReturn(obj); } #undef exec_script_proc @@ -427,7 +425,7 @@ void mf_item_make_explosive(OpcodeContext& ctx) { if (pid > 0 && pidActive > 0) { Explosions::AddToExplosives(pid, pidActive, min, max); } else { - ctx.printOpcodeError("%s() - invalid PID number, must be greater than 0.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid PID number.", ctx.getMetaruleName()); ctx.setReturn(-1); } } @@ -452,7 +450,7 @@ void op_get_proto_data(OpcodeContext& ctx) { long result = -1; fo::Proto* protoPtr; int pid = ctx.arg(0).rawValue(); - if (fo::util::CheckProtoID(pid) && fo::func::proto_ptr(pid, &protoPtr) != result) { + if (fo::util::CheckProtoID(pid) && fo::util::GetProto(pid, &protoPtr)) { result = *(long*)((BYTE*)protoPtr + ctx.arg(1).rawValue()); } else { ctx.printOpcodeError(protoFailedLoad, ctx.getOpcodeName(), pid); @@ -472,14 +470,27 @@ void op_set_proto_data(OpcodeContext& ctx) { } } +static const char* invalidObjPtr = "%s() - invalid object pointer."; + void mf_get_object_data(OpcodeContext& ctx) { + long result = 0; DWORD* object_ptr = (DWORD*)ctx.arg(0).rawValue(); - ctx.setReturn(*(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue())); + if (*(object_ptr - 1) != 0xFEEDFACE && !(fo::var::combat_state & fo::CombatStateFlag::InCombat)) { + ctx.printOpcodeError(invalidObjPtr, ctx.getMetaruleName()); + } else { + result = *(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()); + } + ctx.setReturn(result); } void mf_set_object_data(OpcodeContext& ctx) { DWORD* object_ptr = (DWORD*)ctx.arg(0).rawValue(); - *(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()) = ctx.arg(2).rawValue(); + if (*(object_ptr - 1) != 0xFEEDFACE && !(fo::var::combat_state & fo::CombatStateFlag::InCombat)) { + ctx.printOpcodeError(invalidObjPtr, ctx.getMetaruleName()); + ctx.setReturn(-1); + } else { + *(long*)((BYTE*)object_ptr + ctx.arg(1).rawValue()) = ctx.arg(2).rawValue(); + } } void mf_get_object_ai_data(OpcodeContext& ctx) { @@ -536,7 +547,7 @@ void mf_get_object_ai_data(OpcodeContext& ctx) { value = arrayId; break; default: - ctx.printOpcodeError("%s() - invalid value for AI argument.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid aiParam number.", ctx.getMetaruleName()); } ctx.setReturn(value); } @@ -554,7 +565,7 @@ void mf_set_drugs_data(OpcodeContext& ctx) { result = Drugs::SetDrugAddictTimeOff(pid, val); break; default: - ctx.printOpcodeError("%s() - invalid value for type argument.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid type number.", ctx.getMetaruleName()); return; } if (result) { diff --git a/sfall/Modules/Scripting/Handlers/Stats.cpp b/sfall/Modules/Scripting/Handlers/Stats.cpp index 9f7f5c85e..d2e7cf0ed 100644 --- a/sfall/Modules/Scripting/Handlers/Stats.cpp +++ b/sfall/Modules/Scripting/Handlers/Stats.cpp @@ -54,6 +54,7 @@ void op_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(); + if (stat <= fo::STAT_lu) fo::func::stat_recalc_derived(fo::var::obj_dude); } else { ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } @@ -63,6 +64,7 @@ void op_set_pc_extra_stat(OpcodeContext& ctx) { int stat = ctx.arg(0).rawValue(); if (stat >= 0 && stat < fo::STAT_max_stat) { ((long*)FO_VAR_pc_proto)[44 + stat] = ctx.arg(1).rawValue(); + if (stat <= fo::STAT_lu) fo::func::stat_recalc_derived(fo::var::obj_dude); } else { ctx.printOpcodeError(invalidStat, ctx.getOpcodeName()); } diff --git a/sfall/Modules/Scripting/Handlers/Utils.cpp b/sfall/Modules/Scripting/Handlers/Utils.cpp index 97e6dfdb3..e4424af81 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.cpp +++ b/sfall/Modules/Scripting/Handlers/Utils.cpp @@ -16,14 +16,15 @@ * along with this program. If not, see . */ +#include "Utils.h" + #include "..\..\..\FalloutEngine\Fallout2.h" +#include "..\..\..\Utils.h" #include "..\..\ScriptExtender.h" #include "..\..\Message.h" #include "..\Arrays.h" #include "..\OpcodeContext.h" -#include "Utils.h" - namespace sfall { namespace script @@ -42,7 +43,7 @@ static bool FalloutStringCompare(const char* str1, const char* str2, long codePa if (c1 == c2) continue; if (codePage == 866) { - // replace Russian 'x' to English (Fallout specific) + // replace Russian 'x' with English (Fallout specific) if (c1 == 229) c1 -= 229 - 'x'; if (c2 == 229) c2 -= 229 - 'x'; } @@ -103,7 +104,7 @@ void op_strlen(OpcodeContext& ctx) { void op_atoi(OpcodeContext& ctx) { auto str = ctx.arg(0).strValue(); ctx.setReturn( - static_cast(strtol(str, (char**)nullptr, 0)) // auto-determine radix + static_cast(StrToLong(str, 0)) ); } @@ -197,149 +198,112 @@ void mf_string_compare(OpcodeContext& ctx) { } } +void mf_string_find(OpcodeContext& ctx) { + const char* const haystack = ctx.arg(0).strValue(); + int pos = 0; + if (ctx.numArgs() > 2) { + int len = strlen(haystack); + pos = ctx.arg(2).intValue(); + if (pos >= len) { + ctx.setReturn(-1); + return; + } else if (pos < 0) { + pos += len; + } + } + const char* needle = strstr(haystack + pos, ctx.arg(1).strValue()); + ctx.setReturn( + needle != nullptr ? (int)(needle - haystack) : -1 + ); +} + // A safer version of sprintf for using in user scripts. -static char* sprintf_lite(const char* format, const ScriptValue& value) { - int fmtlen = strlen(format); - int buflen = fmtlen + 1; +static const char* sprintf_lite(OpcodeContext& ctx, const char* opcodeName) { + const char* format = ctx.arg(0).strValue(); + int fmtLen = strlen(format); + if (fmtLen == 0) { + return format; + } + if (fmtLen > 1024) { + ctx.printOpcodeError("%s() - format string exceeds maximum length of 1024 characters.", opcodeName); + return "Error"; + } + int newFmtLen = fmtLen; - for (int i = 0; i < fmtlen; i++) { - if (format[i] == '%') buflen++; // will possibly be escaped, need space for that + for (int i = 0; i < fmtLen; i++) { + if (format[i] == '%') newFmtLen++; // will possibly be escaped, need space for that } // parse format to make it safe - char* newfmt = new char[buflen]; - unsigned char mode = 0; - char specifier = 0; - bool hasDigits = false; + char* newFmt = new char[newFmtLen + 1]; + bool conversion = false; int j = 0; + int valIdx = 0; + char* outBuf = ScriptExtender::gTextBuffer; + long bufCount = ScriptExtender::TextBufferSize() - 1; + int numArgs = ctx.numArgs(); - for (int i = 0; i < fmtlen; i++) { + for (int i = 0; i < fmtLen; i++) { char c = format[i]; - switch (mode) { - case 0: // prefix + if (!conversion) { + // Start conversion. + if (c == '%') conversion = true; + } else { if (c == '%') { - mode = 1; - } - break; - case 1: // definition - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - if (c == 'h' || c == 'l' || c == 'j' || c == 'z' || c == 't' || c == 'L') continue; // ignore sub-specifiers - - if (c == 's' && !value.isString() || // don't allow to treat non-string values as string pointers - c == 'n') // don't allow "n" specifier + conversion = false; // escaped % sign, just skip + } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + // ignore size prefixes + if (c == 'h' || c == 'l' || c == 'j' || c == 'z' || c == 't' || c == 'w' || c == 'L' || c == 'I') continue; + // Type specifier, perform conversion. + if (++valIdx == numArgs) { + ctx.printOpcodeError("%s() - format string contains more conversions than passed arguments (%d): %s", opcodeName, numArgs - 1, format); + } + const auto& arg = ctx.arg(valIdx < numArgs ? valIdx : numArgs - 1); + if (c == 'S' || c == 'Z') { + c = 's'; // don't allow wide strings + } + if (c == 's' && !arg.isString() || // don't allow treating non-string values as string pointers + c == 'n') // don't allow "n" specifier { c = 'd'; } - specifier = c; - mode = 2; - } else if (c == '%') { - mode = 0; - hasDigits = false; - } else if (c >= '0' && c <= '9') { - hasDigits = true; - } - break; - case 2: // postfix - if (c == '%') { // don't allow more than one specifier - newfmt[j++] = '%'; // escape it - if (format[i + 1] == '%') i++; // skip already escaped + newFmt[j++] = c; + newFmt[j] = '\0'; + int partLen = arg.isFloat() + ? _snprintf(outBuf, bufCount, newFmt, arg.floatValue()) + : _snprintf(outBuf, bufCount, newFmt, arg.rawValue()); + outBuf += partLen; + bufCount -= partLen; + conversion = false; + j = 0; + if (bufCount <= 0) { + break; + } + continue; } - break; } - newfmt[j++] = c; + newFmt[j++] = c; } - newfmt[j] = '\0'; - - // calculate required length - if (hasDigits) { - buflen = 254; - } else if (specifier == 'c') { - buflen = j; - } else if (specifier == 's') { - buflen = j + strlen(value.strValue()); - } else { - buflen = j + 30; // numbers + // Copy the remainder of the string. + if (bufCount > 0) { + newFmt[j] = '\0'; + strcpy_s(outBuf, bufCount, newFmt); } - const long bufMaxLen = ScriptExtender::TextBufferSize() - 1; - if (buflen > bufMaxLen - 1) buflen = bufMaxLen - 1; - ScriptExtender::gTextBuffer[bufMaxLen] = '\0'; - - if (value.isFloat()) { - _snprintf(ScriptExtender::gTextBuffer, buflen, newfmt, value.floatValue()); - } else { - _snprintf(ScriptExtender::gTextBuffer, buflen, newfmt, value.rawValue()); - } - delete[] newfmt; + delete[] newFmt; return ScriptExtender::gTextBuffer; } void op_sprintf(OpcodeContext& ctx) { ctx.setReturn( - sprintf_lite(ctx.arg(0).strValue(), ctx.arg(1)) + sprintf_lite(ctx, ctx.getOpcodeName()) ); } void mf_string_format(OpcodeContext& ctx) { - const char* format = ctx.arg(0).strValue(); - - int fmtLen = strlen(format); - if (fmtLen == 0) { - ctx.setReturn(format); - return; - } - if (fmtLen > 1024) { - ctx.printOpcodeError("%s() - the format string exceeds maximum length of 1024 characters.", ctx.getMetaruleName()); - ctx.setReturn("Error"); - } else { - char* newFmt = new char[fmtLen + 1]; - newFmt[fmtLen] = '\0'; - // parse format to make it safe - int i = 0, arg = 0, totalArg = ctx.numArgs(); // total passed args - do { - char c = format[i]; - if (c == '%') { - char cf = format[i + 1]; - if (cf != '%') { - if (arg >= 0) { - arg++; - if (arg == totalArg) arg = -1; // format '%' prefixes in the format string exceed the number of passed value args - } - if (arg < 0) { // have % more than passed value args - c = '^'; // delete % - } - // check string is valid or replace unsupported format - else if ((cf == 's' && (arg > 0 && !ctx.arg(arg).isString())) || (cf != 's' && cf != 'd')) { - newFmt[i++] = c; - c = 'd'; // replace with %d - } - } else { - newFmt[i++] = cf; // skip %% - } - } - newFmt[i] = c; - } while (++i < fmtLen); - - const long bufMaxLen = ScriptExtender::TextBufferSize() - 1; - - switch (totalArg) { - case 2 : - _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue()); - break; - case 3 : - _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue(), ctx.arg(2).rawValue()); - break; - case 4 : - _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue()); - break; - case 5 : - _snprintf(ScriptExtender::gTextBuffer, bufMaxLen, newFmt, ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), ctx.arg(3).rawValue(), ctx.arg(4).rawValue()); - } - ScriptExtender::gTextBuffer[bufMaxLen] = '\0'; // just in case - - delete[] newFmt; - ctx.setReturn(ScriptExtender::gTextBuffer); - } + ctx.setReturn( + sprintf_lite(ctx, ctx.getMetaruleName()) + ); } void op_message_str_game(OpcodeContext& ctx) { diff --git a/sfall/Modules/Scripting/Handlers/Utils.h b/sfall/Modules/Scripting/Handlers/Utils.h index 305d93139..6c96d4137 100644 --- a/sfall/Modules/Scripting/Handlers/Utils.h +++ b/sfall/Modules/Scripting/Handlers/Utils.h @@ -37,6 +37,8 @@ void op_strlen(OpcodeContext&); void mf_string_compare(OpcodeContext&); +void mf_string_find(OpcodeContext&); + void op_sprintf(OpcodeContext&); void mf_string_format(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Worldmap.cpp b/sfall/Modules/Scripting/Handlers/Worldmap.cpp index 477dcc1f1..cd302a49b 100644 --- a/sfall/Modules/Scripting/Handlers/Worldmap.cpp +++ b/sfall/Modules/Scripting/Handlers/Worldmap.cpp @@ -209,13 +209,13 @@ void mf_set_rest_mode(OpcodeContext& ctx) { void mf_set_rest_on_map(OpcodeContext& ctx) { long mapId = ctx.arg(0).rawValue(); if (mapId < 0) { - ctx.printOpcodeError("%s() - invalid map number argument.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid map number.", ctx.getMetaruleName()); ctx.setReturn(-1); return; } long elev = ctx.arg(1).rawValue(); if (elev < -1 || elev > 2) { - ctx.printOpcodeError("%s() - invalid map elevation argument.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid map elevation.", ctx.getMetaruleName()); ctx.setReturn(-1); } else { Worldmap::SetRestMapLevel(mapId, elev, ctx.arg(2).asBool()); @@ -226,7 +226,7 @@ void mf_get_rest_on_map(OpcodeContext& ctx) { long result = -1; long elev = ctx.arg(1).rawValue(); if (elev < 0 || elev > 2) { - ctx.printOpcodeError("%s() - invalid map elevation argument.", ctx.getMetaruleName()); + ctx.printOpcodeError("%s() - invalid map elevation.", ctx.getMetaruleName()); } else { result = Worldmap::GetRestMapLevel(elev, ctx.arg(0).rawValue()); } diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 8a3cb2951..41461ae86 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -26,6 +26,7 @@ #include "Handlers\Core.h" #include "Handlers\FileSystem.h" #include "Handlers\Graphics.h" +#include "Handlers\IniFiles.h" #include "Handlers\Interface.h" #include "Handlers\Inventory.h" #include "Handlers\Math.h" @@ -114,6 +115,8 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x1e1, "set_critical_table", op_set_critical_table, 5, false, 0, {ARG_INT, ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {0x1e2, "get_critical_table", op_get_critical_table, 4, true, 0, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, {0x1e3, "reset_critical_table", op_reset_critical_table, 4, false, 0, {ARG_INT, ARG_INT, ARG_INT, ARG_INT}}, + {0x1e4, "get_sfall_arg", op_get_sfall_arg, 0, true}, + {0x1e5, "set_sfall_return", op_set_sfall_return, 1, false, 0, {ARG_ANY}}, // hook script system will validate type {0x1eb, "get_ini_string", op_get_ini_string, 1, true, -1, {ARG_STRING}}, {0x1ec, "sqrt", op_sqrt, 1, true, 0, {ARG_NUMBER}}, {0x1ed, "abs", op_abs, 1, true, 0, {ARG_NUMBER}}, @@ -175,8 +178,10 @@ static SfallOpcodeInfo opcodeInfoArray[] = { {0x237, "atoi", op_atoi, 1, true, 0, {ARG_STRING}}, {0x238, "atof", op_atof, 1, true, 0, {ARG_STRING}}, {0x239, "scan_array", op_scan_array, 2, true, -1, {ARG_OBJECT, ARG_ANY}}, + {0x23a, "get_tile_fid", op_get_tile_fid, 1, true, 0, {ARG_INT}}, + {0x23b, "modified_ini", op_modified_ini, 0, true}, {0x23c, "get_sfall_args", op_get_sfall_args, 0, true}, - {0x23d, "set_sfall_arg", op_set_sfall_arg, 2, false, 0, {ARG_INT, ARG_INT}}, + {0x23d, "set_sfall_arg", op_set_sfall_arg, 2, false, 0, {ARG_INT, ARG_ANY}}, // hook script system will validate type {0x241, "get_npc_level", op_get_npc_level, 1, true, -1, {ARG_INTSTR}}, {0x242, "set_critter_skill_points", op_set_critter_skill_points, 3, false, 0, {ARG_OBJECT, ARG_INT, ARG_INT}}, {0x243, "get_critter_skill_points", op_get_critter_skill_points, 2, true, 0, {ARG_OBJECT, ARG_INT}}, @@ -287,6 +292,7 @@ void Opcodes::InitNew() { LoadGameHook::OnGameReset() += []() { PipboyAvailableRestore(); ForceEncounterRestore(); // restore if the encounter did not happen + ResetIniCache(); }; if (int unsafe = IniReader::GetIntDefaultConfig("Debugging", "AllowUnsafeScripting", 0)) { @@ -379,8 +385,6 @@ void Opcodes::InitNew() { opcodes[0x1df] = op_get_bodypart_hit_modifier; opcodes[0x1e0] = op_set_bodypart_hit_modifier; - opcodes[0x1e4] = op_get_sfall_arg; - opcodes[0x1e5] = op_set_sfall_return; opcodes[0x1e6] = op_set_unspent_ap_bonus; opcodes[0x1e7] = op_get_unspent_ap_bonus; opcodes[0x1e8] = op_set_unspent_ap_perk_bonus; @@ -408,8 +412,6 @@ void Opcodes::InitNew() { opcodes[0x227] = op_refresh_pc_art; opcodes[0x22c] = op_stop_sfall_sound; - opcodes[0x23a] = op_get_tile_fid; - opcodes[0x23b] = op_modified_ini; opcodes[0x23e] = op_force_aimed_shots; opcodes[0x23f] = op_disable_aimed_shots; opcodes[0x240] = op_mark_movie_played; diff --git a/sfall/Modules/Scripting/Opcodes.h b/sfall/Modules/Scripting/Opcodes.h index 105478734..3f3781b7b 100644 --- a/sfall/Modules/Scripting/Opcodes.h +++ b/sfall/Modules/Scripting/Opcodes.h @@ -32,4 +32,3 @@ class Opcodes { } } - diff --git a/sfall/Modules/Scripting/ScriptValue.cpp b/sfall/Modules/Scripting/ScriptValue.cpp index f360d0f9e..5c8093482 100644 --- a/sfall/Modules/Scripting/ScriptValue.cpp +++ b/sfall/Modules/Scripting/ScriptValue.cpp @@ -23,7 +23,7 @@ namespace sfall namespace script { -ScriptValue::ScriptValue( DataType type, unsigned long value ) +ScriptValue::ScriptValue(DataType type, unsigned long value) { _val.dw = value; _type = type; @@ -135,6 +135,10 @@ unsigned long ScriptValue::rawValue() const { return _val.dw; } +long ScriptValue::intValue() const { + return _val.i; +} + const char* ScriptValue::strValue() const { return _val.str; } diff --git a/sfall/Modules/Scripting/ScriptValue.h b/sfall/Modules/Scripting/ScriptValue.h index 5db54245f..4dd9459c6 100644 --- a/sfall/Modules/Scripting/ScriptValue.h +++ b/sfall/Modules/Scripting/ScriptValue.h @@ -25,7 +25,7 @@ namespace sfall namespace script { -enum class DataType : unsigned long { +enum class DataType : unsigned short { NONE = 0, INT = 1, FLOAT = 2, @@ -57,6 +57,8 @@ class ScriptValue { unsigned long rawValue() const; + long intValue() const; + float floatValue() const; const char* strValue() const; diff --git a/sfall/Modules/Skills.cpp b/sfall/Modules/Skills.cpp index 4b97582e7..1c0c5510d 100644 --- a/sfall/Modules/Skills.cpp +++ b/sfall/Modules/Skills.cpp @@ -333,12 +333,12 @@ void Skills::init() { LoadGameHook::OnGameReset() += ResetOnGameLoad; char buf[512], key[16]; - auto skillsFile = IniReader::GetConfigString("Misc", "SkillsFile", "", MAX_PATH); + auto skillsFile = IniReader::GetConfigString("Misc", "SkillsFile", ""); if (!skillsFile.empty()) { fo::SkillInfo *skills = fo::var::skill_data; const char* file = skillsFile.insert(0, ".\\").c_str(); - if (GetFileAttributes(file) == INVALID_FILE_ATTRIBUTES) return; + if (GetFileAttributesA(file) == INVALID_FILE_ATTRIBUTES) return; multipliers = new double[7 * fo::SKILL_count](); diff --git a/sfall/Modules/Sound.cpp b/sfall/Modules/Sound.cpp index 135b1d9a1..c2bf838c5 100644 --- a/sfall/Modules/Sound.cpp +++ b/sfall/Modules/Sound.cpp @@ -977,6 +977,13 @@ static void __declspec(naked) sfxl_init_hook() { constexpr int SampleRate = 44100; // 44.1kHz +static char mainMenuMusic[12] = {}; +static char worldMapMusic[12] = {}; +static char worldMapCarMusic[12] = {}; +static char endGameMovieMusic0[12] = {}; +static char endGameMovieMusic1[12] = {}; +static char mapLoadingSound[12] = {}; + void Sound::init() { // Set the 44.1kHz sample rate for the primary sound buffer SafeWrite32(0x44FDBC, SampleRate); @@ -1054,6 +1061,25 @@ void Sound::init() { if (IniReader::GetConfigInt("Sound", "AutoSearchSFX", 1)) { HookCalls(sfxl_init_hook, {0x4A9999, 0x4A9B34}); } + + if (IniReader::GetConfigString("Sound", "MainMenuMusic", "", mainMenuMusic, 9)) { + SafeWrite32(0x480A04, (DWORD)&mainMenuMusic); + } + if (IniReader::GetConfigString("Sound", "WorldMapMusic", "", worldMapMusic, 9)) { + SafeWrite32(0x4C2361, (DWORD)&worldMapMusic); + } + if (IniReader::GetConfigString("Sound", "WorldMapCarMusic", "", worldMapCarMusic, 9)) { + SafeWrite32(0x4C236D, (DWORD)&worldMapCarMusic); + } + if (IniReader::GetConfigString("Sound", "EndGameMovieMusic0", "", endGameMovieMusic0, 9)) { + SafeWrite32(0x43F853, (DWORD)&endGameMovieMusic0); + } + if (IniReader::GetConfigString("Sound", "EndGameMovieMusic1", "", endGameMovieMusic1, 9)) { + SafeWrite32(0x440750, (DWORD)&endGameMovieMusic1); + } + if (IniReader::GetConfigString("Sound", "MapLoadingSound", "", mapLoadingSound, 9)) { + SafeWrite32(0x482B9C, (DWORD)&mapLoadingSound); + } } void Sound::exit() { diff --git a/sfall/Modules/Stats.cpp b/sfall/Modules/Stats.cpp index f7918cf66..b5289e3be 100644 --- a/sfall/Modules/Stats.cpp +++ b/sfall/Modules/Stats.cpp @@ -158,7 +158,7 @@ static long RecalcStat(int stat, int statsValue[]) { static void __stdcall StatRecalcDerived(fo::GameObject* critter) { long* proto = CritterStats::GetProto(critter); - if (!proto && fo::func::proto_ptr(critter->protoId, (fo::Proto**)&proto) == -1) return; + if (!proto && !fo::util::GetProto(critter->protoId, (fo::Proto**)&proto)) return; int baseStats[7], levelStats[7]; for (int stat = fo::Stat::STAT_st; stat <= fo::Stat::STAT_lu; stat++) { @@ -188,10 +188,19 @@ static void __declspec(naked) stat_recalc_derived_hack() { } void Stats::UpdateHPStat(fo::GameObject* critter) { - fo::Proto* proto; - if (fo::func::proto_ptr(critter->protoId, &proto) == -1) return; + if (fo::util::IsPartyMember(critter)) return; - if (!engineDerivedStats) { + if (engineDerivedStats) { + if (critter->critter.health > 0) { + long maxHP = fo::func::stat_level(critter, fo::Stat::STAT_max_hit_points); + if (critter->critter.health != maxHP) { + fo::func::debug_printf("\nWarning: %s (PID: %d, ID: %d) has an incorrect value of the max HP stat: %d, adjusted to %d.", + fo::func::critter_name(critter), critter->protoId, critter->id, critter->critter.health, maxHP); + + critter->critter.health = maxHP; + } + } + } else { auto getStatFunc = (derivedHPwBonus) ? fo::func::stat_level : fo::func::stat_get_base; double sum = 0; @@ -199,21 +208,16 @@ void Stats::UpdateHPStat(fo::GameObject* critter) { sum += (getStatFunc(critter, stat) + statFormulas[fo::Stat::STAT_max_hit_points].shift[stat]) * statFormulas[fo::Stat::STAT_max_hit_points].multi[stat]; } long calcStatValue = statFormulas[fo::Stat::STAT_max_hit_points].base + (int)floor(sum); - if (calcStatValue < statFormulas[fo::Stat::STAT_max_hit_points].min) calcStatValue = statFormulas[fo::Stat::STAT_max_hit_points].min; - - if (proto->critter.base.health != calcStatValue) { - fo::func::debug_printf("\nWarning: %s (PID: %d, ID: %d) has an incorrect base value of the max HP stat: %d, adjusted to %d.", - fo::func::critter_name(critter), critter->protoId, critter->id, proto->critter.base.health, calcStatValue); + if (calcStatValue < statFormulas[fo::Stat::STAT_max_hit_points].min) { + calcStatValue = statFormulas[fo::Stat::STAT_max_hit_points].min; + } + fo::Proto* proto; + if (fo::util::GetProto(critter->protoId, &proto) && proto->critter.base.health != calcStatValue) { proto->critter.base.health = calcStatValue; + critter->critter.health = calcStatValue + proto->critter.bonus.health; } } - - // set the current HP to match the max HP stat for non-party member critters - // (prevent full healing for party members when entering random encounter maps) - if (!fo::util::IsPartyMember(critter) && critter->critter.health > 0) { - critter->critter.health = proto->critter.base.health + proto->critter.bonus.health; - } } static void __declspec(naked) stat_set_base_hack_allow() { @@ -284,7 +288,7 @@ void Stats::init() { MakeCall(0x4AF54E, stat_set_base_hack_allow); MakeCall(0x455D65, op_set_critter_stat_hack); // STAT_unused for other critters - auto xpTableList = IniReader::GetConfigList("Misc", "XPTable", "", 2048); + auto xpTableList = IniReader::GetConfigList("Misc", "XPTable", ""); size_t numLevels = xpTableList.size(); if (numLevels > 0) { HookCall(0x434AA7, GetNextLevelXPHook); @@ -301,10 +305,10 @@ void Stats::init() { SafeWrite8(0x4AFB1B, static_cast(numLevels + 1)); } - auto statsFile = IniReader::GetConfigString("Misc", "DerivedStats", "", MAX_PATH); + auto statsFile = IniReader::GetConfigString("Misc", "DerivedStats", ""); if (!statsFile.empty()) { const char* statFile = statsFile.insert(0, ".\\").c_str(); - if (GetFileAttributes(statFile) != INVALID_FILE_ATTRIBUTES) { // check if file exists + if (GetFileAttributesA(statFile) != INVALID_FILE_ATTRIBUTES) { // check if file exists derivedHPwBonus = (IniReader::GetInt("Main", "HPDependOnBonusStats", 0, statFile) != 0); engineDerivedStats = false; diff --git a/sfall/Modules/SubModules/EnginePerks.cpp b/sfall/Modules/SubModules/EnginePerks.cpp index e90f29e03..9fbacf36e 100644 --- a/sfall/Modules/SubModules/EnginePerks.cpp +++ b/sfall/Modules/SubModules/EnginePerks.cpp @@ -17,6 +17,7 @@ */ #include "..\..\main.h" +#include "..\..\Utils.h" #include "..\..\FalloutEngine\Fallout2.h" #include "EnginePerks.h" @@ -26,120 +27,39 @@ namespace sfall namespace perk { -static class EnginePerkBonus { -public: - long WeaponScopeRangePenalty = 8; - long WeaponScopeRangeBonus = 5; - long WeaponLongRangeBonus = 4; - long WeaponAccurateBonus = 20; - long WeaponHandlingBonus = 3; +static long SalesmanBonus = 20; +static long DemolitionExpertBonus = 10; - float MasterTraderBonus = 25; - long SalesmanBonus = 20; - - long LivingAnatomyBonus = 5; - long PyromaniacBonus = 5; - - long StonewallPercent = 50; - - long DemolitionExpertBonus = 10; - - long VaultCityInoculationsPoisonBonus = 10; - long VaultCityInoculationsRadBonus = 10; - - /////////////////////////////////////////// - - void setWeaponScopeRangePenalty(long value) { - if (value < 0) return; - WeaponScopeRangePenalty = value; - SafeWrite32(0x42448E, value); - } - - void setWeaponScopeRangeBonus(long value) { - if (value < 2) return; - WeaponScopeRangeBonus = value; - SafeWrite32(0x424489, value); - } - - void setWeaponLongRangeBonus(long value) { - if (value < 2) return; - WeaponLongRangeBonus = value; - SafeWrite32(0x424474, value); - } - - void setWeaponAccurateBonus(long value) { - if (value < 0) return; - WeaponAccurateBonus = value; - if (WeaponAccurateBonus > 125) WeaponAccurateBonus = 125; - SafeWrite8(0x42465D, static_cast(WeaponAccurateBonus)); - } - - void setWeaponHandlingBonus(long value) { - if (value < 0) return; - WeaponHandlingBonus = value; - if (WeaponHandlingBonus > 10) WeaponHandlingBonus = 10; - SafeWrite8(0x424636, static_cast(WeaponHandlingBonus)); - SafeWrite8(0x4251CE, static_cast(-WeaponHandlingBonus)); - } - - void setMasterTraderBonus(long value) { - if (value < 0) return; - MasterTraderBonus = static_cast(value); - SafeWrite32(0x474BB3, *(DWORD*)&MasterTraderBonus); // write float data - } - - void setSalesmanBonus(long value) { - if (value < 0) return; - SalesmanBonus = value; - if (SalesmanBonus > 999) SalesmanBonus = 999; - } - - void setLivingAnatomyBonus(long value) { - if (value < 0) return; - LivingAnatomyBonus = value; - if (LivingAnatomyBonus > 125) LivingAnatomyBonus = 125; - SafeWrite8(0x424A91, static_cast(LivingAnatomyBonus)); - } - - void setPyromaniacBonus(long value) { - if (value < 0) return; - PyromaniacBonus = value; - if (PyromaniacBonus > 125) PyromaniacBonus = 125; - SafeWrite8(0x424AB6, static_cast(PyromaniacBonus)); - } - - void setStonewallPercent(long value) { - if (value < 0) return; - StonewallPercent = value; - if (StonewallPercent > 100) StonewallPercent = 100; - SafeWrite8(0x424B50, static_cast(StonewallPercent)); - } +static bool TryGetModifiedInt(const char* key, int defaultValue, int& outValue, const char* perksFile) { + outValue = IniReader::GetInt("PerksTweak", key, defaultValue, perksFile); + return outValue != defaultValue; +} - void setDemolitionExpertBonus(long value) { - if (value < 0) return; - DemolitionExpertBonus = value; - if (DemolitionExpertBonus > 999) DemolitionExpertBonus = 999; +static void TryPatchValue8(const char* key, int defaultValue, int minValue, int maxValue, DWORD addr, const char* perksFile) { + int value; + if (TryGetModifiedInt(key, defaultValue, value, perksFile) && value >= minValue) { + SafeWrite8(addr, static_cast(min(value, maxValue))); } +} - void setVaultCityInoculationsPoisonBonus(long value) { - if (value < -100) value = -100; - if (value > 100) value = 100; - VaultCityInoculationsPoisonBonus = value; - SafeWrite8(0x4AF26A, static_cast(VaultCityInoculationsPoisonBonus)); +static void TryPatchValue32(const char* key, int defaultValue, int minValue, int maxValue, DWORD addr, const char* perksFile) { + int value; + if (TryGetModifiedInt(key, defaultValue, value, perksFile) && value >= minValue) { + SafeWrite32(addr, min(value, maxValue)); } +} - void setVaultCityInoculationsRadBonus(long value) { - if (value < -100) value = -100; - if (value > 100) value = 100; - VaultCityInoculationsRadBonus = value; - SafeWrite8(0x4AF287, static_cast(VaultCityInoculationsRadBonus)); - } +static void TryPatchSkillBonus8(const char* key, int defaultValue, DWORD addr, const char* perksFile) { + TryPatchValue8(key, defaultValue, 0, 125, addr, perksFile); +} -} perks; +static void TryPatchSkillBonus32(const char* key, int defaultValue, DWORD addr, const char* perksFile) { + TryPatchValue32(key, defaultValue, 0, 125, addr, perksFile); +} static void __declspec(naked) perk_adjust_skill_hack_salesman() { __asm { - imul eax, [perks.SalesmanBonus]; + imul eax, [SalesmanBonus]; add ecx, eax; // barter_skill + (perkLevel * SalesmanBonus) mov eax, ecx retn; @@ -148,7 +68,7 @@ static void __declspec(naked) perk_adjust_skill_hack_salesman() { static void __declspec(naked) queue_explode_exit_hack_demolition_expert() { __asm { - imul eax, [perks.DemolitionExpertBonus]; + imul eax, [DemolitionExpertBonus]; add ecx, eax; // maxBaseDmg + (perkLevel * DemolitionExpertBonus) add ebx, eax // minBaseDmg + (perkLevel * DemolitionExpertBonus) retn; @@ -162,42 +82,52 @@ void EnginePerkBonusInit() { } void ReadPerksBonuses(const char* perksFile) { - int wScopeRangeMod = IniReader::GetInt("PerksTweak", "WeaponScopeRangePenalty", 8, perksFile); - if (wScopeRangeMod != 8) perks.setWeaponScopeRangePenalty(wScopeRangeMod); - wScopeRangeMod = IniReader::GetInt("PerksTweak", "WeaponScopeRangeBonus", 5, perksFile); - if (wScopeRangeMod != 5) perks.setWeaponScopeRangeBonus(wScopeRangeMod); - - int wLongRangeBonus = IniReader::GetInt("PerksTweak", "WeaponLongRangeBonus", 4, perksFile); - if (wLongRangeBonus != 4) perks.setWeaponLongRangeBonus(wLongRangeBonus); - - int wAccurateBonus = IniReader::GetInt("PerksTweak", "WeaponAccurateBonus", 20, perksFile); - if (wAccurateBonus != 20) perks.setWeaponAccurateBonus(wAccurateBonus); - - int wHandlingBonus = IniReader::GetInt("PerksTweak", "WeaponHandlingBonus", 3, perksFile); - if (wHandlingBonus != 3) perks.setWeaponHandlingBonus(wHandlingBonus); - - int masterTraderBonus = IniReader::GetInt("PerksTweak", "MasterTraderBonus", 25, perksFile); - if (masterTraderBonus != 25) perks.setMasterTraderBonus(masterTraderBonus); - - int salesmanBonus = IniReader::GetInt("PerksTweak", "SalesmanBonus", 20, perksFile); - if (salesmanBonus != 20) perks.setSalesmanBonus(salesmanBonus); - - int livingAnatomyBonus = IniReader::GetInt("PerksTweak", "LivingAnatomyBonus", 5, perksFile); - if (livingAnatomyBonus != 5) perks.setLivingAnatomyBonus(livingAnatomyBonus); - - int pyromaniacBonus = IniReader::GetInt("PerksTweak", "PyromaniacBonus", 5, perksFile); - if (pyromaniacBonus != 5) perks.setPyromaniacBonus(pyromaniacBonus); - - int stonewallPercent = IniReader::GetInt("PerksTweak", "StonewallPercent", 50, perksFile); - if (stonewallPercent != 50) perks.setStonewallPercent(stonewallPercent); - - int demolitionExpertBonus = IniReader::GetInt("PerksTweak", "DemolitionExpertBonus", 10, perksFile); - if (demolitionExpertBonus != 10) perks.setDemolitionExpertBonus(demolitionExpertBonus); - - int vaultCityInoculationsBonus = IniReader::GetInt("PerksTweak", "VaultCityInoculationsPoisonBonus", 10, perksFile); - if (vaultCityInoculationsBonus != 10) perks.setVaultCityInoculationsPoisonBonus(vaultCityInoculationsBonus); - vaultCityInoculationsBonus = IniReader::GetInt("PerksTweak", "VaultCityInoculationsRadBonus", 10, perksFile); - if (vaultCityInoculationsBonus != 10) perks.setVaultCityInoculationsRadBonus(vaultCityInoculationsBonus); + int value; + TryPatchValue32("WeaponScopeRangePenalty", 8, 0, 100, 0x42448E, perksFile); + TryPatchValue32("WeaponScopeRangeBonus", 5, 2, 100, 0x424489, perksFile); + TryPatchValue32("WeaponLongRangeBonus", 4, 2, 100, 0x424474, perksFile); + TryPatchSkillBonus8("WeaponAccurateBonus", 20, 0x42465D, perksFile); + if (TryGetModifiedInt("WeaponHandlingBonus", 3, value, perksFile) && value >= 0) { + if (value > 10) value = 10; + SafeWrite8(0x424636, static_cast(value)); + SafeWrite8(0x4251CE, static_cast(-value)); + } + if (TryGetModifiedInt("MasterTraderBonus", 25, value, perksFile) && value >= 0) { + float floatValue = static_cast(value); + SafeWrite32(0x474BB3, *(DWORD*)&floatValue); // write float data + } + if (TryGetModifiedInt("SalesmanBonus", SalesmanBonus, value, perksFile) && value >= 0) { + SalesmanBonus = min(value, 999); + } + TryPatchSkillBonus8("LivingAnatomyBonus", 5, 0x424A91, perksFile); + TryPatchSkillBonus8("LivingAnatomyDoctorBonus", 10, 0x496E66, perksFile); + TryPatchSkillBonus8("PyromaniacBonus", 5, 0x424AB6, perksFile); + TryPatchValue8("StonewallPercent", 50, 0, 100, 0x424B50, perksFile); + if (TryGetModifiedInt("DemolitionExpertBonus", DemolitionExpertBonus, value, perksFile) && value >= 0) { + DemolitionExpertBonus = min(value, 999); + } + if (TryGetModifiedInt("VaultCityInoculationsPoisonBonus", 10, value, perksFile)) { + SafeWrite8(0x4AF26A, static_cast(clamp(value, -100, 100))); + } + if (TryGetModifiedInt("VaultCityInoculationsRadBonus", 10, value, perksFile)) { + SafeWrite8(0x4AF287, static_cast(clamp(value, -100, 100))); + } + TryPatchSkillBonus8("VaultCityTrainingFirstAidBonus", 5, 0x496E35, perksFile); + TryPatchSkillBonus8("VaultCityTrainingDoctorBonus", 5, 0x496E7F, perksFile); + TryPatchSkillBonus32("MedicFirstAidBonus", 10, 0x496E19, perksFile); + TryPatchSkillBonus32("MedicDoctorBonus", 10, 0x496E4E, perksFile); + + TryPatchSkillBonus32("GhostBonus", 20, 0x496EA9, perksFile); + TryPatchSkillBonus8("ThiefBonus", 10, 0x496EC1, perksFile); + TryPatchSkillBonus8("MasterThiefBonus", 15, 0x496EE0, perksFile); + TryPatchSkillBonus8("HarmlessBonus", 20, 0x496F02, perksFile); + TryPatchSkillBonus32("SpeakerBonus", 20, 0x496F1B, perksFile); + TryPatchSkillBonus8("ExpertExcrementExpeditorBonus", 5, 0x496F33, perksFile); + TryPatchSkillBonus8("NegotiatorBonus", 10, 0x496F48, perksFile); + TryPatchSkillBonus32("GamblerBonus", 20, 0x496F79, perksFile); + TryPatchSkillBonus32("RangerOutdoorsmanBonus", 15, 0x496F95, perksFile); + TryPatchSkillBonus8("SurvivalistBonus", 25, 0x496FAB, perksFile); + TryPatchSkillBonus8("MrFixitBonus", 10, 0x496E00, perksFile); } } diff --git a/sfall/Modules/SubModules/WindowRender.cpp b/sfall/Modules/SubModules/WindowRender.cpp index a5803aae1..4a892856d 100644 --- a/sfall/Modules/SubModules/WindowRender.cpp +++ b/sfall/Modules/SubModules/WindowRender.cpp @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +#include + #include "..\..\main.h" #include "..\..\FalloutEngine\Fallout2.h" diff --git a/sfall/Modules/TalkingHeads.cpp b/sfall/Modules/TalkingHeads.cpp index b26e85da0..01f0012ed 100644 --- a/sfall/Modules/TalkingHeads.cpp +++ b/sfall/Modules/TalkingHeads.cpp @@ -121,7 +121,7 @@ static bool LoadFrm(Frm* frm) { int pathLen = sprintf_s(buf, "%s\\art\\heads\\%s\\", fo::var::patches, frm->path); if (pathLen > 250) return false; - if (!(GetFileAttributes(frm->path) & FILE_ATTRIBUTE_DIRECTORY)) { + if (!(GetFileAttributesA(frm->path) & FILE_ATTRIBUTE_DIRECTORY)) { frm->broken = 1; return false; } @@ -139,13 +139,13 @@ static bool LoadFrm(Frm* frm) { } if (frm->magic != 0xABCD) { // frm file not patched StrAppend(buf, "highlight.off", pathLen); - if (GetFileAttributes(buf) != INVALID_FILE_ATTRIBUTES) frm->showHighlights = 1; // disable all highlights + if (GetFileAttributesA(buf) != INVALID_FILE_ATTRIBUTES) frm->showHighlights = 1; // disable all highlights if (!frm->showHighlights && texHighlight) { StrAppend(buf, "highlight.on", pathLen); - if (GetFileAttributes(buf) != INVALID_FILE_ATTRIBUTES) frm->showHighlights = 2; // show textured highlights + if (GetFileAttributesA(buf) != INVALID_FILE_ATTRIBUTES) frm->showHighlights = 2; // show textured highlights } StrAppend(buf, "background.off", pathLen); - if (GetFileAttributes(buf) != INVALID_FILE_ATTRIBUTES) frm->bakedBackground = 1; // fills entire frame surface with a key-color + if (GetFileAttributesA(buf) != INVALID_FILE_ATTRIBUTES) frm->bakedBackground = 1; // fills entire frame surface with a key-color } frm->textures = textures; texMap.emplace(std::piecewise_construct, std::forward_as_tuple(frm->key), diff --git a/sfall/Modules/Unarmed.cpp b/sfall/Modules/Unarmed.cpp index 4c5cb62b2..e933fb781 100644 --- a/sfall/Modules/Unarmed.cpp +++ b/sfall/Modules/Unarmed.cpp @@ -424,10 +424,10 @@ void Unarmed::init() { unarmed = Hits(); - auto unarmedFile = IniReader::GetConfigString("Misc", "UnarmedFile", "", MAX_PATH); + auto unarmedFile = IniReader::GetConfigString("Misc", "UnarmedFile", ""); if (!unarmedFile.empty()) { const char* file = unarmedFile.insert(0, ".\\").c_str(); - if (GetFileAttributes(file) != INVALID_FILE_ATTRIBUTES) { // check if file exists + if (GetFileAttributesA(file) != INVALID_FILE_ATTRIBUTES) { // check if file exists char stat[6] = "Stat0"; char sHit[4] = "0"; for (size_t i = 0; i < Hits::count; _itoa(++i, sHit, 10)) { @@ -510,7 +510,8 @@ void Unarmed::init() { "PiercingKick" }; for (size_t i = 0; i < 14; i++) { - hitNames[i] = Translate::Get("Unarmed", setting[i], "", 17); + hitNames[i] = Translate::Get("Unarmed", setting[i], ""); + if (hitNames[i].size() > 16) hitNames[i].resize(16); // trim to fit } } diff --git a/sfall/Modules/Unarmed.h b/sfall/Modules/Unarmed.h index 80d8528fd..1d6370368 100644 --- a/sfall/Modules/Unarmed.h +++ b/sfall/Modules/Unarmed.h @@ -27,7 +27,6 @@ class Unarmed : public Module { public: const char* name() { return "Unarmed"; } void init(); - //void exit() override; static long GetHitAPCost(fo::AttackType hit); static long GetDamage(fo::AttackType hit, long &minOut, long &maxOut); diff --git a/sfall/SafeWrite.h b/sfall/SafeWrite.h index ea834eda6..fb34f8e15 100644 --- a/sfall/SafeWrite.h +++ b/sfall/SafeWrite.h @@ -17,6 +17,10 @@ enum CodeType : BYTE { JumpZ = 0x74, // 0x74 [jz short ...] }; +// Macros for quick replacement of assembler opcodes pushad/popad +#define pushadc __asm push eax __asm push edx __asm push ecx +#define popadc __asm pop ecx __asm pop edx __asm pop eax + template void __stdcall SafeWrite(DWORD addr, T data) { DWORD oldProtect; diff --git a/sfall/Translate.cpp b/sfall/Translate.cpp index d2d3edb01..80a7f9fda 100644 --- a/sfall/Translate.cpp +++ b/sfall/Translate.cpp @@ -37,12 +37,12 @@ size_t Translate::Get(const char* section, const char* setting, const char* defa return IniReader::GetString(section, setting, defaultValue, buffer, bufSize, translationIni.File()); } -std::string Translate::Get(const char* section, const char* setting, const char* defaultValue, size_t bufSize) { - return std::move(IniReader::GetString(section, setting, defaultValue, bufSize, translationIni.File())); +std::string Translate::Get(const char* section, const char* setting, const char* defaultValue) { + return std::move(IniReader::GetString(section, setting, defaultValue, translationIni.File())); } -std::vector Translate::GetList(const char* section, const char* setting, const char* defaultValue, char delimiter, size_t bufSize) { - return std::move(IniReader::GetList(section, setting, defaultValue, bufSize, delimiter, translationIni.File())); +std::vector Translate::GetList(const char* section, const char* setting, const char* defaultValue, char delimiter) { + return std::move(IniReader::GetList(section, setting, defaultValue, delimiter, translationIni.File())); } //////////////////////////////////////////////////////////////////////////////// @@ -59,7 +59,7 @@ static void MakeLangTranslationPath(const char* config) { while (*iniDef == '\\' || *iniDef == '/' || *iniDef == '.') iniDef++; // skip first characters sprintf(translationIni.lang, "%s\\text\\%s\\%s", patches, language, iniDef); - translationIni.state = (GetFileAttributes(translationIni.lang) != INVALID_FILE_ATTRIBUTES); + translationIni.state = (GetFileAttributesA(translationIni.lang) != INVALID_FILE_ATTRIBUTES); } static std::string saveSfallDataFailMsg; diff --git a/sfall/Translate.h b/sfall/Translate.h index f3591cb99..13512e140 100644 --- a/sfall/Translate.h +++ b/sfall/Translate.h @@ -29,10 +29,10 @@ class Translate { static size_t Get(const char* section, const char* setting, const char* defaultValue, char* buffer, size_t bufSize = 128); // Translates given string using sfall translation INI file - static std::string Get(const char* section, const char* setting, const char* defaultValue, size_t bufSize = 128); + static std::string Get(const char* section, const char* setting, const char* defaultValue); // Translates given list of strings using sfall translation INI file - static std::vector GetList(const char* section, const char* setting, const char* defaultValue, char delimiter, size_t bufSize = 256); + static std::vector GetList(const char* section, const char* setting, const char* defaultValue, char delimiter); /* Messages */ diff --git a/sfall/Utils.cpp b/sfall/Utils.cpp index 4b9c8ca2d..cbf2c7617 100644 --- a/sfall/Utils.cpp +++ b/sfall/Utils.cpp @@ -34,11 +34,11 @@ void trim(char* str) { int len = strlen(str) - 1; int i = len; - while (len >= 0 && isSpace(str[len])) len--; + while (len >= 0 && isspace(str[len])) len--; if (i != len) str[len + 1] = '\0'; // delete all spaces on the right i = 0; - while (i < len && isSpace(str[i])) i++; + while (i < len && isspace(str[i])) i++; if (i > 0) { int j = 0; do { @@ -51,10 +51,6 @@ void ToLowerCase(std::string& line) { std::transform(line.begin(), line.end(), line.begin(), ::tolower); } -bool isSpace(char c) { - return (c == ' ' || c == '\t' /*|| c == '\n' || c == '\r'*/); -} - // returns position, find word must be lowercase const char* strfind(const char* source, const char* word) { if (source == 0 || word == 0 || *word == 0) return 0; @@ -69,7 +65,7 @@ const char* strfind(const char* source, const char* word) { return 0; } -// replace all '/' chars to '\' +// replace all '/' chars with '\' void StrNormalizePath(char* path) { if (*path == 0) return; do { @@ -77,9 +73,23 @@ void StrNormalizePath(char* path) { } while (*(++path) != 0); } -// max range 0-32767 -long GetRandom(long min, long max) { - return (min + (std::rand() % (max - (min - 1)))); +long StrToLong(const char* str, int base /* = 0 */) { + // Trim leading whitespaces. + while (isspace(static_cast(*str))) ++str; + // Support 0b prefix to detect binary values (for compatibility with GetPrivateProfile* functions). + if ((base == 0 || base == 2) && str[0] == '0' && str[1] == 'b' && str[2] != '\0') { + str = &str[2]; + base = 2; + } + char* end; + errno = 0; + return strtol(str, &end, base); // see https://stackoverflow.com/a/6154614 } + +// max range 0-32767 +//long GetRandom(long min, long max) { // uncomment the srand() in main.cpp before use +// return (min + (std::rand() % (max - (min - 1)))); +//} + } diff --git a/sfall/Utils.h b/sfall/Utils.h index 16c513595..a376a0c55 100644 --- a/sfall/Utils.h +++ b/sfall/Utils.h @@ -4,7 +4,7 @@ #include #include -namespace sfall +namespace sfall { // splits a string by given delimiter @@ -21,6 +21,13 @@ void split(const std::string &s, char delim, T result, size_t limit = -1) { } } +template +T clamp(T value, T min, T max) { + if (value < min) return min; + if (value > max) return max; + return value; +} + WORD ByteSwapW(WORD w); DWORD ByteSwapD(DWORD dw); @@ -33,12 +40,34 @@ void trim(char* str); void ToLowerCase(std::string& line); -bool isSpace(char c); - const char* strfind(const char* source, const char* word); void StrNormalizePath(char* path); -long GetRandom(long min, long max); +// Uses standard strtol with base of 0 (auto, support 0x for hex and 0 for octal) and an addition of 0b prefix for binary. +long StrToLong(const char* str, int base = 0); + +//long GetRandom(long min, long max); + + +// Case-insensitive less +// Taken from https://stackoverflow.com/a/1801913 +struct ci_less +{ + // case-independent (ci) compare_less binary function + struct nocase_compare + { + bool operator() (const unsigned char& c1, const unsigned char& c2) const { + return tolower(c1) < tolower(c2); + } + }; + bool operator() (const std::string &s1, const std::string &s2) const { + return std::lexicographical_compare( + s1.begin (), s1.end (), // source range + s2.begin (), s2.end (), // dest range + nocase_compare () // comparison + ); + } +}; } diff --git a/sfall/ddraw.vcxproj b/sfall/ddraw.vcxproj index 13a74296f..e0093a329 100644 --- a/sfall/ddraw.vcxproj +++ b/sfall/ddraw.vcxproj @@ -28,7 +28,7 @@ DynamicLibrary NotSet true - v141 + v140 DynamicLibrary @@ -196,6 +196,7 @@ MachineX86 false $(DXSDK_DIR)lib\x86\ + /EMITPOGOPHASEINFO %(AdditionalOptions) "$(ProjectDir)postbuild.cmd" release "$(TargetPath)" @@ -260,6 +261,7 @@ MachineX86 false $(DXSDK_DIR)lib\x86\ + /EMITPOGOPHASEINFO %(AdditionalOptions) "$(ProjectDir)postbuild.cmd" releasexp "$(TargetPath)" @@ -343,6 +345,8 @@ + + @@ -416,6 +420,7 @@ + @@ -494,6 +499,8 @@ + + @@ -552,6 +559,7 @@ + diff --git a/sfall/ddraw.vcxproj.filters b/sfall/ddraw.vcxproj.filters index ae1ca2556..e46e2bf0b 100644 --- a/sfall/ddraw.vcxproj.filters +++ b/sfall/ddraw.vcxproj.filters @@ -452,6 +452,11 @@ HLSL + + + + Modules\Scripting\Handlers + @@ -824,6 +829,11 @@ Modules\SubModules + + + + Modules\Scripting\Handlers + diff --git a/sfall/ddraw_CheckAddr.ini b/sfall/ddraw_CheckAddr.ini index adc11e01c..29fef5e31 100644 --- a/sfall/ddraw_CheckAddr.ini +++ b/sfall/ddraw_CheckAddr.ini @@ -1,49 +1,39 @@ ;sfall configuration settings - version for checking conflicting addresses. [Main] -;Change to 1 if you want to use command line args to tell sfall to use another ini file. +;Set to 1 to enable the built-in High Resolution Patch mode that is similar to the hi-res patch by Mash +;The required settings will be read from the f2_res.ini configuration file of the original hi-res patch +HiResMode=1 + +;Set to 1 if you want to use command line arguments to tell sfall to use another ini file +;This option is always enabled in 4.3.3/3.8.33 or later. The information is left for reference only UseCommandLine=0 ;Uncomment and point to a file to get alternate translations for some sfall messages -;TranslationsINI=.\Translations.ini +;This file can be placed in text\\ for sfall to get the translations depending on the game language +;TranslationsINI=sfall\Translations.ini ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [ExtraPatches] -;This section allows you to set multiple paths to folders containing mods or patches that will be loaded by the game -;The priority of read files will be higher than the files in patchXXX.dat -;If DataLoadOrderPatch is enabled, the data load order will be: -;master_patches > critter_patches > PatchFile99 - PatchFile0 > patchXXX.dat > ... -;Paths to folders and Fallout .dat files are supported. The available range for PatchFile option names is from 0 to 99 -;The greater numbers take precedence over lesser numbers +;This section allows you to set multiple paths to folders containing mods or patches +;Paths to folders and Fallout .dat files are supported +;The PatchFileXX options are available from 0 to 99. Larger numbers take precedence over smaller numbers (same as patchXXX.dat) +;Starting from 4.4/3.8.40, the game will load custom .dat files and folders from \mods\mods_order.txt +;The files and folders in mods_order.txt will have a higher priority than the PatchFileXX options +;The complete order of how the engine loads game data is: +;master_patches > critter_patches > mods_order.txt > PatchFileXX > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat ;PatchFile0=mods\RP_data +;PatchFile1= -;Path to the folder in which the game will automatically search and load custom .dat files -;The data load order of the .dat files in the folder will be in reverse alphabetical order of the filenames -;The .dat files specified in the PatchFileXX options will have higher priority than others -;Uncomment the option to specify a different folder path. The default path is \mods\ -;AutoSearchPath=mods - -;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -[Sound] -;Sets the number of allowed simultaneous sound effects -;Set to 0 to leave the default unchanged (i.e. 4) -NumSoundBuffers=16 - -;Set to 1 to allow attaching sound files to combat float messages -AllowSoundForFloats=1 - -;Set to 1 to automatically search for alternative formats (mp3/wma/wav) when Fallout tries to play an acm -;Set to 2 to play alternative music files even if original acm files are not present in the music folder -;This does not effect the play_sfall_sound and stop_sfall_sound script functions -AllowDShowSound=2 - -;Set to 1 to override the music path used by default (i.e. data\sound\music\) if not present in the cfg -;Set to 2 to overwrite all occurances of the music path -OverrideMusicDir=2 +;Some details about mods_order.txt: +;If mods_order.txt does not exist, sfall will create one in \mods\ +;To install a new mod, copy the file/folder to \mods\ and add its name to mods_order.txt +;Mods will be loaded line by line from top to bottom. You can change the load order in a text editor +;To disable a mod temporarily, you can comment it out by adding a ';' or '#' to the beginning of the line ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Speed] -;Set to 0 to disable everything in this section +;Set to 1 to enable options related to the game speed adjustment (SpeedMulti#, SpeedKey#, SpeedModKey, and SpeedToggleKey) Enable=1 ;The speeds corresponding to each slot in percent. (i.e. 100 is normal speed) @@ -61,16 +51,15 @@ SpeedMulti9=900 ;The initial speed at game startup SpeedMultiInitial=110 -;Set to 1 to also affect the playback speed of mve video files without an audio track -AffectPlayback=1 - ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Graphics] ;Set to 0 for 8 bit fullscreen ;Set to 4 for DX9 fullscreen ;Set to 5 for DX9 windowed +;Set to 6 for DX9 fullscreen windowed (the resolution in f2_res.ini should be set to the same aspect ratio as your desktop resolution) ;A DX9 mode is required for any graphics related script extender functions to work (i.e. fullscreen shaders) ;Modes 1, 2 and 3 are no longer supported +;If using the hi-res patch by Mash, this option will always be read from the main ddraw.ini file Mode=5 ;If using a DX9 mode, this changes the resolution @@ -79,21 +68,37 @@ Mode=5 GraphicsWidth=1280 GraphicsHeight=720 +;Window position data. Do not modify +;Set to -1 or 0 to reset the window position to the center or top-left corner +WindowData=-1 + +;Uncomment the option to use a hardware shader (requires DX9 graphics mode) +;The shader file .fx must be placed in \\shaders\ and must contain one technique with one or more passes +;You can specify multiple shader files, separated by commas +;GlobalShaderFile=global.fx + +;Set to 1 to automatically enable linear texture filtering when the scale factor is not an integer +;Set to 2 to force-enable linear texture filtering +;This can be used in conjunction with the GlobalShaderFile option +TextureFilter=1 + ;Set to 1 to do the palette conversion on the GPU -;Set to 2 to do the palette conversion on the CPU +;Set to 2 to do the palette conversion on the CPU (used for compatibility with old video cards) ;Set to 0 to pick automatically ;GPU is faster, but requires v2.0 pixel shader support GPUBlt=0 ;Set to 1 to allow using 32-bit textures for talking heads -;The texture files should be placed in art\heads\\ (w/o extension) +;The texture files should be placed in art\heads\\ (without extension) ;The files in the folder should be numbered according to the number of frames in the talking head FRM file (0.png, 1.png, etc.) ;See the text file in the modders pack for a detailed description ;Requires DX9 graphics mode and v2.0 pixel shader support (see GPUBlt option) Use32BitHeadGraphics=1 -;Set to 1 to automatically search for alternative avi video files when Fallout tries to play the game movies -;Requires DX9 graphics mode 4 or 5 +;Set to 1 to automatically search for alternative AVI video files when Fallout tries to play the game movies +;Set to 2 to force AVI videos to fit the screen width +;Requires DX9 graphics mode +;The recommended video codec is Xvid AllowDShowMovies=1 ;Fade effect time percentage modifier @@ -102,17 +107,61 @@ FadeMultiplier=101 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Interface] +;Set to 1 to expand the number of action points displayed on the interface bar +;Requires new IFACE_E.frm and HR_IFACE_E.frm files in art\intrface\ (included in sfall.dat) to display correctly +;The minimum supported version of High Resolution Patch is 4.1.8 +ActionPointsBar=1 + ;Set to 1 to use the expanded world map interface ;Set to 2 to skip correcting the position of entrance markers on town maps ;You can use resized FRMs in 700x682 for town maps in the expanded world map interface ;Requires High Resolution Patch v4.1.8 and a new WORLDMAP.frm file in art\intrface\ (included in sfall.dat) -;The resolution of hi-res patch must be set to at least 890x720 +;The resolution of the hi-res patch must be set to at least 890x720 ExpandWorldMap=1 -;Set to 1 to expand the number of action points displayed on the interface bar -;Requires new IFACE_E.frm and HR_IFACE_E.frm files in art\intrface\ (included in sfall.dat) to display correctly -;The minimum supported version of High Resolution Patch is 4.1.8 -ActionPointsBar=1 +;Set to 1 to draw a dotted line while traveling on the world map (similar to Fallout 1) +WorldMapTravelMarkers=1 +;Uncomment these lines to change the appearance of the markers +;The color index in Fallout default palette (valid range: 1..228; default is 134) +;TravelMarkerColor=134 +;The length and spacing of the dots in pixels for each type of terrain in worldmap.txt +;Syntax is 'length:spacing', with each pair separated by a comma (valid range: 1..10; default is 2) +;TravelMarkerStyles=2:2,2:2,2:2,2:2 + +;Set to 1 to display terrain types when hovering the cursor over the player's marker on the world map +WorldMapTerrainInfo=1 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +[Sound] +;Sets the number of allowed simultaneous sound effects +;Set to 0 to leave the default unchanged (i.e. 8). The maximum is 32 +NumSoundBuffers=16 + +;Set to 1 to allow attaching sound files to combat float messages +AllowSoundForFloats=1 + +;Set to 1 to automatically search for alternative formats (mp3/wma/wav) when Fallout tries to play an ACM +;Alternative music files will play even if original ACM files are not present in the music folder +;This does not effect the play_sfall_sound and stop_sfall_sound script functions +AllowDShowSound=1 + +;Set to 1 to override the default music path with data\sound\music\ if music_path is not present in the cfg +;Set to 2 to overwrite all occurrences of the music path +OverrideMusicDir=2 + +;Set to 1 to automatically search for new SFX sound files at game startup +;Note: With this option enabled, you will no longer need to use the utility regsnd.exe to register new SFX sounds +;This will slightly increase the startup time of the game on older computers +AutoSearchSFX=1 + +;Uncomment these lines to override the names of sound files used by the engine +;Filenames are limited to 8 characters (without extension) +;MainMenuMusic=07desert +;WorldMapMusic=23world +;WorldMapCarMusic=20car +;EndGameMovieMusic0=akiss +;EndGameMovieMusic1=10labone +;MapLoadingSound=wind2 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Input] @@ -120,7 +169,7 @@ ActionPointsBar=1 UseScrollWheel=1 ;The mouse Z position is divided by this modifier to calculate the number of inventory -;slots to scroll. My mouse moves 120 pixel in the z direction for one click of the mouse +;slots to scroll. My mouse moves 120 pixels in the z direction for one click of the mouse ;wheel, but this may vary depending on your mouse manufacturer and windows settings. ;Set to 0 to only ever scroll 1 click ScrollMod=0 @@ -130,9 +179,9 @@ ScrollMod=0 MouseSensitivity=100 ;DX scancode of a key to press when the middle mouse button is clicked -;The default of 0x30 toggles between your two weapons +;The default of 48 ('B' key) toggles between your active items ;Set to 0 to disable -MiddleMouse=0x30 +MiddleMouse=48 ;Set to 1 to reverse the left and right mouse buttons ReverseMouseButtons=0 @@ -150,7 +199,7 @@ BackgroundMouse=0 SpeedModKey=-1 ;A key to press to toggle the speed tweak on or off -;Specify 0 if you don't want a toggle key, or a DX scancode otherwise +;Set to 0 if you don't want a toggle key, or a DX scancode otherwise SpeedToggleKey=0 ;The keys corresponding to the 10 speed slots @@ -166,12 +215,12 @@ SpeedKey7=0x00 SpeedKey8=0x00 SpeedKey9=0x00 -;A key to hold down to move the window around when using graphics mode 5 +;A key to hold down to move the window around when using DX9 graphics mode 5 ;Set to 0 if you don't want to use a modifier key, or a DX scancode otherwise ;Set to -1 for either ctrl key, -2 for either alt key or -3 for either shift key WindowScrollKey=0 -;A key to press to reload your currently equipped weapon +;A key to press to reload your currently equipped weapon or use the active item ;Set to 0 if you don't want a reload key, or a DX scancode otherwise ReloadWeaponKey=0 @@ -185,7 +234,7 @@ FastMoveFromContainer=0 ;A key to press to open a debug game editor ;Set to 0 to disable, or a DX scancode otherwise -;Requires sfall debugging mode and FalloutClient.exe from the modders pack +;Requires sfall debugging mode and FalloutDebug.exe from the modders pack DebugEditorKey=0 ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX @@ -193,10 +242,11 @@ DebugEditorKey=0 ;Changes some of Fallout 2 engine functions to Fallout 1 behavior: ;- disables playing the final movie/credits after the endgame slideshow ;- disables halving the weight for power armor items +;- endgame_movie script function plays movie 10 or 11 based on the player's gender before the credits Fallout1Behavior=1 ;Time limit in years. Must be between -3 and 13 -;Set to 0 if you want to die the instant you leave arroyo +;Set to 0 if you want to die the instant you leave Arroyo ;Set to -1 to remove the time limit, and automatically reset the date back to 2241 each time you would have reached it ;Set to -2 or -3 to remove the time limit, automatically reset the date, but override Fallout's GetDate function to return the correct year TimeLimit=-1 @@ -210,6 +260,7 @@ WorldMapTimeMod=100 WorldMapFPSPatch=1 ;Controls the world map speed if WorldMapFPSPatch is 1. Higher values cause slower movement +;Default is 66 milliseconds WorldMapDelay2=66 ;Set to 1 to enable Ray's patch to make world map encounter rate independent of your travel speed @@ -218,8 +269,8 @@ WorldMapEncounterFix=1 WorldMapEncounterRate=5 ;The number of slots available in the locations list panel of the world map -;Set to 0 to leave unchanged. 17 is default. -;Setting this greater than 17 requires a replacement background FRM, or you'll get glitched graphics +;Set to 0 to leave the default unchanged (i.e. 17). The maximum is 127 +;Setting this greater than 17 requires a replacement WMTABS.frm file in art\intrface\, or you'll get glitched graphics WorldMapSlots=17 ;To start a new game somewhere other than artemple.map, uncomment the next line and set it to the map you want to load @@ -232,31 +283,37 @@ VersionString=Test ;To use a config file other than fallout2.cfg, uncomment the next line and add the name of your new file ;ConfigFile= -;Set to 1 to enable functions relating to overriding the file system +;Set to 1 to enable script functions relating to overriding the file system UseFileSystemOverride=1 ;To use a patch file other than patch000.dat, uncomment the next line and add your new file name ;If you want to load multiple patch files (up to 1000) at once, you can include a %d in the file name (sprintf syntax) PatchFile=patch%03d.dat -;Set to 1 to use the modified data load order for the engine to find game data -;Original: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat -;Modified: master_patches > critter_patches > patchXXX.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat +;Set to 1 to change the order of how the engine loads game data +;Old: patchXXX.dat > critter_patches > critter_dat > f2_res_patches > f2_res_dat > master_patches > master_dat +;New: master_patches > critter_patches > [ExtraPatches] > patchXXX.dat > sfall.dat > critter_dat > f2_res_patches > f2_res_dat > master_dat +;This option is always enabled in 4.3/3.8.30 or later. The information is left for reference only DataLoadOrderPatch=1 -;Set to 1 to enable loading alternative dialog msg files from text\\dialog_female\ for female PC +;Set to 1 to load alternative dialog msg files from text\\dialog_female\ for female PC ;Set to 2 to also load subtitle files from text\\cuts_female\ for female PC FemaleDialogMsgs=1 +;Set to 1 to allow using the caret character '^' in dialog msg files to specify the alternative text based on the player's gender +;The text must be enclosed in angle brackets (example: ) +DialogGenderWords=1 + ;To change the default and starting player models, uncomment the next four lines. -;The default models can also be changed ingame via script +;The default models can also be changed in-game via script ;MaleStartModel=hmwarr ;MaleDefaultModel=hmjmps ;FemaleStartModel=hfprim ;FemaleDefaultModel=hfjmps -;To change the various ingame movies, modify the next 17 lines -;Most of these can also be changed ingame via script. +;To change the various in-game movies, modify the next 17 lines +;You can also define additional movies by adding Movie18 - Movie32 lines +;Most of these can also be changed in-game via script Movie1=iplogo.mve Movie2=intro.mve Movie3=elder.mve @@ -283,13 +340,10 @@ StartDay=25 ;To change the limit of the distance away from the player to which you're allowed to scroll the local maps, uncomment the next two lines ;Defaults are 480 in the x direction and 400 in the y direction. -;Not compatible with the res patch! +;Does not work with the hi-res patch by Mash! LocalMapXLimit=480 LocalMapYLimit=400 -;To add additional perks to the game, uncomment the next line and set it to point to a file containing perk information -;PerksFile=Perks.ini - ;Set to 1 if you want the pipboy to be available at the start of the game ;Set to 2 to make the pipboy available by only skipping the vault suit movie check PipBoyAvailableAtGameStart=2 @@ -306,7 +360,7 @@ ExtraKillTypes=1 ;0 - Fallout default ;1 - Glovz's Damage Fix ;2 - Glovz's Damage Fix with Damage Multiplier tweak -;5 - Haenlomal's Yet Another Ammo Mod. +;5 - Haenlomal's Yet Another Ammo Mod DamageFormula=0 ;Prevents you from using 0 to escape from dialogue at any time. @@ -315,7 +369,7 @@ DialogueFix=1 ;Prevents you from using number keys to enter unvisited areas on a town map TownMapHotkeysFix=1 -;Set to 1 to disable the horrigan encounter +;Set to 1 to disable the Horrigan encounter DisableHorrigan=1 ;Set to 1 to disable the random element in NPC levelling. @@ -332,24 +386,21 @@ ViewYPos=200 ;Set to 1 to force Fallout not to use multiple processor cores even if they are available SingleCore=0 -;Set to 1 to override the art_chache_size setting in fallout2.cfg +;Set to 1 to override the art_cache_size setting in fallout2.cfg OverrideArtCacheSize=1 ;Prevents you from saving in combat except at the start of your turn to avoid a few bugs -;Note that even with this option enabled, it is still not advisable to save in combat at all. +;Note that even with this option enabled, it is still not advisable to save in combat ;Set to 2 to block all saving in combat SaveInCombatFix=1 -;Point to an ini file containing elevator data -ElevatorsFile=nofile.ini - ;Uncomment and set a comma delimited list of numbers to use a custom xp table. ;Player's level is capped once the highest specified level is reached XPTable=50,100,200 -;Set to 1 to enable additional weapon animation codes from o-t +;Set to 1 to enable additional weapon animation codes from 'o' to 't' ;The 4 byte value at 0x39 of weapon protos may range from 0 to 15 rather than 0 to 10 -;Since the letters 'n' and 'r' are in use for other animations, an animation code of 11 corrisponds to 's' and 15 to 't' +;Since the letters 'n' and 'r' are in use for other animations, an animation code of 11 corresponds to 's' and 15 to 't' AdditionalWeaponAnims=1 ;Uncomment these lines to modify the default modifiers for aimed shots at specific bodyparts @@ -371,8 +422,6 @@ AdditionalWeaponAnims=1 ;If the ExtraKillTypes option is enabled, this should be set to 3, with containing entries for any new types ;Must be non-zero to use the edit/get/reset_critical script functions OverrideCriticalTable=2 -;To change the path and filename of the critical table file, uncomment the next line -;OverrideCriticalFile=CriticalOverrides.ini ;Set to 1 to get notification of karma changes in the notification window DisplayKarmaChanges=0 @@ -383,18 +432,15 @@ AlwaysReloadMsgs=1 ;Set to 1 to force the player to play the idle animation when reloading their weapon PlayIdleAnimOnReload=1 -;Set to 1 to prevent corpses from blocking line of fire -CorpseLineOfFireFix=1 - ;Changes the timer (in days) for deleting corpses on a map after you leave (valid range: 0..13) -;The corpses of critters with 'Ages' flag or on maps with 'dead_bodies_age=No' set in maps.txt will not disappear +;The corpses of critters with 'Ages' flag set or on maps with 'dead_bodies_age=No' set in maps.txt will not disappear ;Default is 6. Set to 0 for a 12-hour timer CorpseDeleteTime=1 ;Set a number of milliseconds to idle each input loop ;Set to -1 to disable -;Set to 0 to idle only if other processes are waiting for processor time -;Set to 1 (or some higher number if needed, the maximum is 127) to prevent 100% CPU use +;Set to 0 to idle only if other processes are waiting for processor time (WinXP/2000: if processes have equal priority) +;Set to 1 (or some higher number if needed) to prevent 100% CPU use. The maximum is 50 ProcessorIdle=1 ;Set to 1 if using the hero appearance mod @@ -411,20 +457,13 @@ SkipOpeningMovies=2 ;Set to 0 to disable NPCsTryToSpendExtraAP=3 -;Set to 1 to fix NPCs not checking weapon perks properly when choosing the best weapon in combat -;Set to 2 to change the priority multiplier for having weapon perk to 3x (the original is 5x) -;Note that enabling this option can significantly affect the weapon of choice for some NPCs -AIBestWeaponFix=2 - -;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as drug use preference when using drugs in their inventory -AIDrugUsePerfFix=1 - -;Allows the use of tiles over 80x36 in size. sfall will just split and resave them at startup -;Set to 1 to check all tiles on started (slow) -;Set to 2 if you provide a XLtiles.lst file in art\tiles\ containing a list of the tile ids that need checking +;Allows the use of tiles over 80x36 in size. sfall will just split and resave them to art\tiles\zzz####.frm at startup +;Set to 1 to check all tiles on started (slow, but can also be useful for checking the correct size of your new tiles) +;Set to 2 if you provide a XLtiles.lst file in art\tiles\ containing a list of the tile indexes that need checking AllowLargeTiles=1 ;Set to 1 to boost the maximum number of tile FRMs from 4096 to 16383 +;This option is always enabled in 4.3.7/3.8.37 or later. The information is left for reference only MoreTiles=1 ;Change the Skilldex cursor FRM numbers @@ -463,11 +502,12 @@ ConsoleOutputPath="bingle.txt" ExtraSaveSlots=1 ;To use more than one save slot for quick saving (F6 key) without picking a slot beforehand, set the next two lines -;AutoQuickSave sets how many save slots on a page you want to use for quick saving (valid range: 1..10) -;AutoQuickSavePage is the page number to use (valid range: 1..1000) if ExtraSaveSlots is enabled -;The quick saves will be rotated from the first slot on the page to the n-th slot -;Set to 0 to disable. AutoQuickSave will use the current selected page if AutoQuickSavePage is disabled +;Quick save will cyclically overwrite saves from the first slot on the specified page to the last slot on the n-th page +;AutoQuickSave sets how many pages you want to use for quick saving (valid range: 1..10) +;Set to 0 to disable AutoQuickSave=1 +;AutoQuickSavePage is the page number to start at if ExtraSaveSlots is enabled (valid range: 0..999) +;Set to -1 to use the current selected page (not recommended) AutoQuickSavePage=1 ;Set to 1 to speed up the HP/AC counter animations @@ -484,10 +524,10 @@ KarmaFRMs=47,48,49 ScienceOnCritters=1 ;Modify this value to change the player's speed of rotation on the inventory and character screens -;Default is 166 +;Default is 166 (lower - faster; valid range: 0..1000) SpeedInventoryPCRotation=220 -;Modify the number of the extra interface boxes available to modders. (Default is 5, and the maximum is 95) +;Modify the number of the extra interface boxes available to modders (Default is 5, and the maximum is 95) BoxBarCount=15 ;Uncomment to set the text colour of the extra interface boxes @@ -500,47 +540,39 @@ BonusHtHDamageFix=1 ;Set to 1 to display additional points of damage from Bonus HtH/Ranged Damage perks in the inventory DisplayBonusDamage=0 -;Set to 1 to display the range of the second attack mode in the inventory when you switch weapon modes in active item slots -DisplaySecondWeaponRange=1 +;Modify the maximum number of animations allowed to run on a map (Default is 32, and the maximum is 127) +AnimationsAtOnceLimit=64 -;Modify the maximum number of animations allowed to run on a map. (Default is 32, and the maximum is 127) -AnimationsAtOnceLimit=120 - -;Set to 1 to remove the limits that stop the player rolling critical successes/misses in the first few days of game time +;Set to 1 to remove the limits that stop the game from rolling critical successes/failures in the first few days of game time RemoveCriticalTimelimits=1 -;Change the colour of the font used on the main menu for the Fallout/sfall version number and copyright text +;Change the colour of the font used on the main menu for the Fallout/sfall version string and copyright text ;It's the last byte ('3C' by default) that picks the colour used. The first byte supplies additional flags for this option +;1 - change the colour for the version string only +;2 - underline text for the version string +;4 - use monospace font for the version string ;MainMenuFontColour=0x00003C ;Change the colour of the font used on the main menu for the button text MainMenuBigFontColour=0x3C -;Two alternate fixes to the interaction between HtH attacks and the fast shot trait -;0 - Fallout 2 original behaviour -;1 - Haenlomal's fix, called shots are enabled for HtH attacks -;2 - Restoring the -1 AP bonus for HtH attacks (i.e. Fallout 1 behaviour) +;Alternative behaviors to the Fast Shot trait +;0 - Fallout 2 original behavior: -1 AP cost for ranged weapons. Aimed attacks are disabled +;1 - Haenlomal's tweak: aimed attacks are enabled for melee/unarmed weapons and HtH attacks +;2 - Alternative behavior: apply -1 AP cost to melee/unarmed weapons and HtH attacks +;3 - Fallout 1 original behavior: -1 AP cost for all weapons. Aimed attacks are disabled FastShotFix=2 +;Set to 1 to fix the carry weight penalty of the Small Frame trait not being applied to bonus Strength points +SmallFrameFix=1 + ;Set to 1 to boost the maximum number of script names from 1450 to 10000 BoostScriptDialogLimit=1 -;Allows you to edit the skill tables -;Point the next line to an ini file containing the replacement skill data -;SkillsFile=Skills.ini - -;To change the relationship between SPECIAL stats and derived stats, uncomment the next line -;See the Stats.ini in the modders pack for an example file -;DerivedStats=Stats.ini - -;Allows you to change some parameters for drugs and their addictions -;See the Drugs.ini in the modders pack for an example file -;DrugsFile=Drugs.ini - ;These options modify the checks to see if a critter can carry an additional item, changing which items are counted towards the weight limit and adding an additional size check ;Set the mode to 0 to disable the size check, 1 to apply to the PC only, 2 to apply to the PC and party members, or 3 to apply to all critters ;Only the PC uses CritterInvSizeLimit. Other critters will use the unused stat (STAT_unused = 10) or have the size limit of 100 if the stat is not set ;Add 4 to the mode to limit the weight check to used items only -;You can use line number 542/543 in proto.msg and line number 35 in inventry.msg to set up custom messages for item size +;You can use message number 542/543 in proto.msg and message number 35 in inventry.msg to set up custom messages for item size CritterInvSizeLimitMode=6 CritterInvSizeLimit=200 @@ -549,9 +581,11 @@ CritterInvSizeLimit=200 ;2 - Motion sensor doesn't require charges MotionScannerFlags=3 -;Set to non-0 to adjust the maximum encounter table size -;Default is 40, and the maximum is 127 -EncounterTableSize=127 +;Set a value greater than 40 to change the maximum encounter table size (enc_## in worldmap.txt) +;Default is 40, and the maximum is 100 +;Note: Setting this greater than 50 requires renumbering all message lines for the encounter tables in worldmap.msg +;The messages for each table must be numbered from (3000 + table number * 100) to (3099 + table number * 100) +EncounterTableSize=100 ;Set to 1 to disable the pipboy alarm button DisablePipboyAlarm=1 @@ -569,33 +603,12 @@ SuperStimExploitFix=1 InventoryApCost=3 QuickPocketsApCostReduction=2 -;Set to 1 to allow objects seeing through other objects that have their ShootThru flag set -;Note that enabling this option can cause unexpected NPC behavior in some situations -ObjCanSeeObj_ShootThru_Fix=1 - -;Set to 1 to fix the broken obj_can_hear_obj script function -ObjCanHearObjFix=1 - -;Set to 1 to enable the mood argument of start_gdialog script function to be available for talking heads -;If the argument value is -1, the mood will be determined by the local variable 0 of the script (vanilla behavior) -StartGDialogFix=1 - -;Set to 1 to fix and repurpose the unused called_shot/num_attacks arguments of attack_complex script function -;This also changes the behavior of the result flags arguments -;called_shot - additional damage, when the damage received by the target is above the specified minimum damage -;num_attacks - the number of free action points on the first turn only -;attacker_results - unused, must be 0 or not equal to the target_results argument when specifying result flags for the target -AttackComplexFix=1 - -;Set to 1 to enable the balanced bullet distribution formula for burst attacks -ComputeSprayMod=1 - -;These options modify the bullet distribution of burst attacks if ComputeSprayMod is 1 -;All the bullets are divided into three groups: central, left and right -;These three groups will then travel along three parallel tracks, trying to hit targets on the way -;CenterMult/Div set the ratio of how many bullets go to the central group, and remaining bullets are divided equally to left and right sides -;TargetMult/Div set the ratio of how many bullets in the central group will attack the primary target directly -;Multipliers are capped at divisor values +;These options modify the bullet distribution of burst attacks +;All the bullets are divided into three groups: center, left, and right +;These groups will then travel along three parallel tracks, trying to hit targets on the way +;CenterMult/Div set the ratio of how many bullets go to the center group, and the remaining are divided equally to the left and right sides +;TargetMult/Div set the ratio of how many bullets in the center group will attack the primary target directly +;Multiplier values are capped at divisor values ComputeSpray_CenterMult=1 ComputeSpray_CenterDiv=3 ComputeSpray_TargetMult=1 @@ -616,17 +629,11 @@ MovieTimer_artimer2=181 MovieTimer_artimer3=271 MovieTimer_artimer4=359 -;Set to 1 to enable the new arrays behavior -;Set to 0 for backward compatibility with pre-3.4 scripts -arraysBehavior=1 - -;Set to 1 to add proper checks if there is enough ammo to use weapons that use multiple ammo per shot +;Set to 1 to add proper checks for ammo before attacking +;By default, a weapon can perform an attack with at least one ammo, regardless of ammo cost calculation +;Note that enabling this option will prevent super cattle prods and mega power fists from attacking with only one ammo left CheckWeaponAmmoCost=1 -;To add additional books to the game, uncomment the next line and point to a file containing book information -;See the Books.ini in the modders pack for an example file -;BooksFile=Books.ini - ;Controls the speed of combat panel animations (lower - faster; valid range: 0..65535) CombatPanelAnimDelay=500 ;Controls the speed of dialog panel animations (lower - faster; valid range: 0..255) @@ -634,9 +641,6 @@ DialogPanelAnimDelay=16 ;Controls the speed of pipboy alarm clock animations (lower - faster; valid range: 0..127) PipboyTimeAnimDelay=25 -;Set to 1 to stack empty identical weapons, no matter what type of ammo was loaded before -StackEmptyWeapons=1 - ;Changes the way weapon reloading works when you drag ammo onto a weapon in the inventory ;Set to -1 to disable (vanilla behavior with the 'Move Items' window) ;Set to 0 to use all the ammo boxes to reload @@ -654,19 +658,18 @@ EnableMusicInDialogue=1 DontTurnOffSneakIfYouRun=1 ;Changes the distance at which the player will switch to walking when trying to use objects (valid range: 0..3) -;Set to 0 to disable switching. (Default is 3) +;Default is 3. Set to 0 to disable switching UseWalkDistance=2 -;Set to 1 to fix the bug of being unable to sell used geiger counters or stealth boys -CanSellUsedGeiger=1 +;Changes the displayed message when you recover from the negative effects of radiation exposure +;The value is the message number in misc.msg (Default is 3003: 'You feel better') +RadEffectsRemovalMsg=3003 -;Set to 1 to fix the issue with being able to charge the car by using cells on other scenery/critters -;Set to 0 if another mod you're using has custom vehicles -CarChargingFix=1 +;Set to 1 to display messages about radiation for the active geiger counter +ActiveGeigerMsgs=1 -;Set to 1 to prevent the car from being lost when entering a location via the Town/World button and then leaving on foot -;Note that the global variable 633 (GVAR_CAR_PLACED_TILE) will be set to -1 when the player leaves a location -CarPlacedTileFix=1 +;Set to 1 to fix the bug of being unable to sell used geiger counters or stealth boys +CanSellUsedGeiger=1 ;Set to 1 to skip weapon equip/unequip animations when performing various actions InstantWeaponEquip=1 @@ -684,8 +687,9 @@ NumbersInDialogue=1 ;Set to 1 to use Fallout's normal text font instead of DOS-like font on the world map WorldMapFontPatch=1 -;Set to 1 to keep the selected attack mode when moving the weapon between active item slots -KeepWeaponSelectMode=1 +;Set to 1 to use Fallout's normal text font for death screen subtitles +;Requires changing the color of subtitles in death.pal palette to white color (index 220) to display the text correctly +DeathScreenFontPatch=1 ;Set to 1 to display full item description for weapon/ammo in the barter screen FullItemDescInBarter=1 @@ -696,34 +700,119 @@ DisplaySwiftLearnerExp=1 ;Set to 1 to display party member's current level/AC/addict flag on the combat control panel PartyMemberExtraInfo=1 +;Set to 1 to skip loading all game settings except the game/combat difficulty from saved games +;Set to 2 to also skip loading the game/combat difficulty settings +SkipLoadingGameSettings=2 + +;Changes the base value of the duration of the knockout effect (valid range: 35..100; default is 35) +;The formula for the duration in ticks is: 10 * (value - 3 * EN) +KnockoutTime=36 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +; Critical modding options - the following options should be changed with caution +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +;Set to 1 to fix the issue with being able to charge the car by using cells on other scenery/critters +;Set to 0 if another mod you're using has custom vehicles +CarChargingFix=1 + +;Set to 1 to prevent the car from being lost when entering a location via the Town/World button and then leaving on foot +;Note that the global variable 633 (GVAR_CAR_PLACED_TILE) will be set to -1 when the player leaves a location +CarPlacedTileFix=1 + +;Set to 1 to fix obj_can_see_obj script function to allow critters to see through objects with 'ShootThru' flag set and other critters in front of them +;Note that enabling this option can cause unexpected NPC behavior in some cases, e.g. initiating combat or dialogue when the player is behind certain obstacles +ObjCanSeeObj_ShootThru_Fix=1 + +;Set to 1 to fix the broken obj_can_hear_obj script function +ObjCanHearObjFix=1 + +;Set to 1 to enable the 'mood' argument of start_gdialog script function for talking heads +;If the argument value is -1, the mood will be determined by the local variable 0 of the script (vanilla behavior) +StartGDialogFix=1 + +;Set to 1 to fix attacker_results/target_results arguments and repurpose the unused called_shot/num_attacks arguments of attack_complex script function +;New behavior of the arguments: +;called_shot - additional damage when hitting the target +;num_attacks - the number of free action points on the first turn only +AttackComplexFix=1 + +;Set to 1 to make the create_object_sid script function run the 'start' procedure of attached script upon object creation +;By default, the 'start' procedure of attached script is executed after the current script procedure is finished +;Note that enabling this option may cause unexpected results with some existing game scripts +CreateObjectSidFix=1 + +;Set to 1 to fix the execution of the 'use_p_proc' procedure for grave type containers when they are in the open state +;By default, using open graves does not run the 'use_p_proc' procedure of attached script like other containers +;Note that enabling this option will cause problems for existing grave scripts +GraveContainersFix=1 + +;Set to 1 to fix the priority score calculation for choosing the best weapon for NPCs +;Note that enabling this option can affect the weapon of choice for some NPCs +AIBestWeaponFix=1 + +;Set to 1 to fix NPCs not taking chem_primary_desire in AI.txt as a preference list when using drugs in their inventory +;Set to 2 to allow NPCs to use only the drugs listed in chem_primary_desire and healing drugs (stimpaks and healing powder) +;Note: chem_primary_desire without fixes prevents the specified item from being consumed if all three values in the list are the same PID +;chem_primary_desire also works as a priority list of drug items the NPC will try to pick up in combat when they are on the ground +AIDrugUsePerfFix=1 + ;Set to 1 to fix the bug of using First Aid/Doctor skills when using them on the player -;This will cause the party member to apply his/her skills when you use First Aid/Doctor skills on the player, but only if +;This will cause the party member to perform First Aid/Doctor skills when you use them on the player, but only if ;the player is standing next to the party member ;Note that because the related engine function is not fully implemented, enabling this option without a global script ;that overrides First Aid/Doctor functions has very limited usefulness PartyMemberSkillFix=1 -;Set to 1 to skip loading all game settings except the game/combat difficulty from a saved game -;Set to 2 to also skip loading the game/combat difficulty settings -SkipLoadingGameSettings=2 - -;Set to 1 to prevent the inventory/loot/automap interfaces from being placed on top of other script-created windows -InterfaceDontMoveOnTop=1 - ;Overrides the global variable number used to show the special death message of the Modoc toilet explosion ;Set to -1 to disable the special death message when the global variable is set SpecialDeathGVAR=-1 ;Set to 1 to disable the special handling of map IDs 19 and 37 in the engine when entering the maps -;Note that enabling this option can break the map changes in Modoc and Vault 15 +;Note that enabling this option will break the map changes in Modoc and Vault 15 in Fallout 2 DisableSpecialMapIDs=1 -;Changes the base value of the duration of the knockout effect (valid range: 35..100; default is 35) -;The formula for the duration in ticks is: 10 * (value - 3 * EN) -KnockoutTime=36 +;Set to 1 to disable the special handling of city areas 45 and 46 in the engine when visiting Area 45: +;Area 45 automatically disappears from the world map, and Area 46 appears on the world map +;Note that enabling this option will break the location change of the 'Fake Vault 13' in Fallout 2 +DisableSpecialAreas=1 + +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +; Configuration ini files +;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -;Set to 1 to display sfall built-in credits at the bottom of credits.txt contents instead of at the top -CreditsAtBottom=0 +;To change the path and filename of the critical table file, uncomment the next line +;OverrideCriticalFile=sfall\CriticalOverrides.ini + +;To change the relationship between SPECIAL stats and derived stats, uncomment the next line +;See the Stats.ini in the modders pack for an example file +;DerivedStats=sfall\Stats.ini + +;Allows you to edit the skill tables +;Point the next line to an ini file containing the replacement skill data +;SkillsFile=sfall\Skills.ini + +;To add additional perks to the game, uncomment the next line and point to a file containing perk information +;See the Perks.ini in the modders pack for an example file +;PerksFile=sfall\Perks.ini + +;To add additional books to the game, uncomment the next line and point to a file containing book information +;See the Books.ini in the modders pack for an example file +;BooksFile=sfall\Books.ini + +;Allows you to change some parameters for drugs and their addictions +;See the Drugs.ini in the modders pack for an example file +;DrugsFile=sfall\Drugs.ini + +;Point to an ini file containing elevator data +;ElevatorsFile=sfall\Elevators.ini + +;Allows you to change the requirements and effects of unarmed attacks +;See the Unarmed.ini in the modders pack for an example file +;UnarmedFile=sfall\Unarmed.ini + +;To change some engine parameters for the game mechanics, uncomment the next line +;TweaksFile=sfall\Tweaks.ini ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Scripts] @@ -732,23 +821,37 @@ CreditsAtBottom=0 ;Paths outside of scripts folder are supported GlobalScriptPaths=scripts\gl*.int,scripts\sfall\gl*.int -;Uncomment the option to specify a common folder path for all ini files used by scripts -;You will have to put all the available ini files of the mods in this directory -;The maximum length of the specified path is 61 characters -;IniConfigFolder=IniConfig +;Uncomment the option to specify an additional directory for ini files used by scripts +;The game will search for ini files first relative to this directory and then relative to the root directory if not found +;The path length is limited to 61 characters +;IniConfigFolder=mods\iniConfigs ;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX [Debugging] ;Extra sfall configuration settings that can be used by modders -;This section is not effected by the 'UseCommandLine' option. It will always be read from the main ini +;This section is not affected by the 'UseCommandLine' option. It will always be read from the main ddraw.ini file ;Set to 1 to enable sfall debugging mode -Enable=11 +Enable=1 + +;Set to 1 to give scripts direct access to Fallout's address space, and to make arbitrary calls into Fallout's code +;Set to 2 to also disable the memory address check in unsafe script functions +;Does not require sfall debugging mode +AllowUnsafeScripting=0 + +;If you're testing changes to the Fallout exe, you can override the CRC that sfall looks for here +;You can use several hex values, separated by commas +;Does not require sfall debugging mode +;ExtraCRC=0x00000000,0x00000000 + +;Set to 1 to skip the compatibility mode check +;Does not require sfall debugging mode +SkipCompatModeCheck=0 ;Fallout 2 Debug Patch -;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both the screen and debug.log +;Set to 1 to send debug output to the screen, 2 to a debug.log file, or 3 to both ;Does not require sfall debugging mode -;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg: +;While you don't need to create an environment variable, you do still need to set the appropriate lines in fallout2.cfg ;------- ;[debug] ;mode=environment @@ -757,38 +860,26 @@ Enable=11 ;show_script_messages=1 ;show_tile_num=1 ;[sound] -;debug=1 -;debug_sfxc=1 +;debug=0 +;debug_sfxc=0 ;------- DebugMode=3 ;Set to 1 to hide error messages in debug output when a null value is passed to the function as an object HideObjIsNullMsg=1 -;Change to 1 to skip the compatibility mode check -SkipCompatModeCheck=0 - -;Set to 1 to skip the executable file size check -;Does not require sfall debugging mode -SkipSizeCheck=0 - -;If you're testing changes to the Fallout exe, you can override the CRC that sfall looks for here -;You can use several hex values, separated by commas -;Does not require sfall debugging mode -;ExtraCRC=0x00000000,0x00000000 +;A key to press to toggle the display of the hex grid on the map on or off +;Set to 0 to disable, or a DX scancode otherwise +MapGridToggleKey=0 ;Set to 1 to stop Fallout from deleting non read-only protos at startup ;Has pretty nasty side effects when saving/reloading, so don't use for regular gameplay DontDeleteProtos=0 -;Set to 1 to give scripts direct access to Fallout's address space, and to make arbitrary calls into Fallout's code -;Does not require sfall debugging mode -AllowUnsafeScripting=0 - ;Set to 1 to force sfall to inject all hooks code into the game, even if corresponding hook scripts don't exist InjectAllGameHooks=1 -;Set to 1 to force sfall to search for global scripts every time the game loads rather than only once on the first game start +;Set to 1 to force sfall to search for global/hook scripts every time the game loads rather than only the first time AlwaysFindScripts=0 ;Set to 1 to force critters to display combat float messages @@ -796,7 +887,7 @@ AlwaysFindScripts=0 Test_ForceFloats=1 ;These options control what output is saved in the debug log (sfall-log.txt) -;Prints messages duing sfall initialization +;Prints messages during sfall initialization Init=1 ;Prints messages relating to hook scripts Hook=0 @@ -804,3 +895,21 @@ Hook=0 Script=0 ;Prints messages relating to the critical table Criticals=0 +;Prints messages relating to engine fixes +Fixes=0 + +;Duplicates logs to a dedicated console window alongside the game window +;This option uses bit flags to control the types of messages to be shown +;All types other than sfall log (bit 1) require DebugMode to be enabled +;1 (bit 0) - debug output from the engine +;2 (bit 1) - sfall log +;4 (bit 2) - messages from debug_msg script function +;8 (bit 3) - messages from display_msg script function +ConsoleWindow=0b0000 + +;Console window position and size data. Do not modify +;Clear the data to reset the window position and size +ConsoleWindowData= + +;Set the code page for the console window (Default is your system code page) +ConsoleCodePage=0 diff --git a/sfall/ducible.exe b/sfall/ducible.exe new file mode 100644 index 000000000..ed53db165 Binary files /dev/null and b/sfall/ducible.exe differ diff --git a/sfall/main.cpp b/sfall/main.cpp index 471ba4e20..f07f5cec8 100644 --- a/sfall/main.cpp +++ b/sfall/main.cpp @@ -75,6 +75,7 @@ #include "Modules\Unarmed.h" #include "Modules\Worldmap.h" +#include "ConsoleWindow.h" #include "CRC.h" #include "InputFuncs.h" #include "Logging.h" @@ -86,6 +87,8 @@ #include "HRP\Init.h" +#include + ddrawDll ddraw; namespace sfall @@ -100,7 +103,7 @@ char falloutConfigName[65]; static void InitModules() { dlogr("In InitModules", DL_INIT); - auto& manager = ModuleManager::getInstance(); + auto& manager = ModuleManager::instance(); // initialize all modules manager.add(); // fixes should be applied at the beginning @@ -204,12 +207,16 @@ static HMODULE SfallInit() { char filepath[MAX_PATH]; GetModuleFileName(0, filepath, MAX_PATH); + auto initStart = std::chrono::high_resolution_clock::now(); + SetCursor(LoadCursorA(0, IDC_ARROW)); ShowCursor(1); if (!CRC(filepath)) return 0; + IniReader::instance().init(); LoggingInit(); + ConsoleWindow::instance().init(); // enabling debugging features isDebug = (IniReader::GetIntDefaultConfig("Debugging", "Enable", 0) != 0); @@ -257,7 +264,7 @@ static HMODULE SfallInit() { HANDLE h = CreateFileA(overrideIni.c_str(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0); if (h != INVALID_HANDLE_VALUE) { CloseHandle(h); - IniReader::SetConfigFile(overrideIni.c_str()); + IniReader::instance().setConfigFile(overrideIni.c_str()); } else { MessageBoxA(0, "You gave a command line argument to Fallout, but the configuration ini file was not found.\n" "Using default ddraw.ini instead.", "Warning", MB_TASKMODAL | MB_ICONWARNING); @@ -265,11 +272,9 @@ static HMODULE SfallInit() { } } else { defaultIni: - IniReader::SetDefaultConfigFile(); + IniReader::instance().setDefaultConfigFile(); } - std::srand(GetTickCount()); - - IniReader::init(); + //std::srand(GetTickCount()); if (IniReader::GetConfigString("Misc", "ConfigFile", "", falloutConfigName, 65)) { dlogr("Applying config file patch.", DL_INIT); @@ -289,6 +294,11 @@ static HMODULE SfallInit() { if (HRP::Setting::ExternalEnabled()) ShowCursor(0); fo::var::setInt(FO_VAR_GNW95_hDDrawLib) = (long)ddraw.sfall; + + auto initEnd = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(initEnd - initStart); + dlog_f("Sfall initialized in: %d us\n", DL_INIT, duration.count()); + return ddraw.sfall; } diff --git a/sfall/main.h b/sfall/main.h index 8ce73d395..8cf1e6905 100644 --- a/sfall/main.h +++ b/sfall/main.h @@ -81,10 +81,6 @@ namespace sfall // Trap for Debugger #define BREAKPOINT __asm int 3 -// Macros for quick replacement of assembler opcodes pushad/popad -#define pushadc __asm push eax __asm push edx __asm push ecx -#define popadc __asm pop ecx __asm pop edx __asm pop eax - extern bool versionCHI; extern char falloutConfigName[65]; diff --git a/sfall/postbuild.cmd b/sfall/postbuild.cmd new file mode 100644 index 000000000..794c4fc87 --- /dev/null +++ b/sfall/postbuild.cmd @@ -0,0 +1,21 @@ +@ECHO OFF + +rem debug, release, etc. +SET type=%1 +rem full path to the compiled DLL +SET target=%2 + +::SET destination=d:\GAMES\Fallout2\@RP\ddraw.dll + +SET pdb="%~dpn2.pdb" + +IF EXIST ducible.exe ( + IF EXIST %pdb% ( + ducible %target% %pdb% + ) ELSE ( + ducible %target% + ) +) + +::echo Copying %target% to %destination% ... +::copy %target% %destination% \ No newline at end of file diff --git a/sfall/postbuild.cmd.template b/sfall/postbuild.cmd.template index 9465d3caa..09c573bca 100644 --- a/sfall/postbuild.cmd.template +++ b/sfall/postbuild.cmd.template @@ -7,5 +7,15 @@ SET target=%2 SET destination=d:\GAMES\Fallout2\@RP\ddraw.dll +SET pdb="%~dpn2.pdb" + +IF EXIST ducible.exe ( + IF EXIST %pdb% ( + ducible %target% %pdb% + ) ELSE ( + ducible %target% + ) +) + echo Copying %target% to %destination% ... copy %target% %destination% \ No newline at end of file diff --git a/sfall/version.h b/sfall/version.h index 4d6cfadb3..1b98980e1 100644 --- a/sfall/version.h +++ b/sfall/version.h @@ -23,8 +23,8 @@ #define LEGAL_COPYRIGHT "Copyright (C) 2006-2023, sfall Team" #define VERSION_MAJOR 4 -#define VERSION_MINOR 3 -#define VERSION_BUILD 8 +#define VERSION_MINOR 4 +#define VERSION_BUILD 0 #define VERSION_REV 0 -#define VERSION_STRING "4.3.8" +#define VERSION_STRING "4.4"