Repository: alexbatalov/fallout2-re Branch: main Commit: b135fc46ef40 Files: 262 Total size: 4.1 MB Directory structure: gitextract_y86dh7pr/ ├── .clang-format ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ └── ci-build.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── LICENSE.md ├── README.md ├── src/ │ ├── game/ │ │ ├── ability.c │ │ ├── ability.h │ │ ├── actions.c │ │ ├── actions.h │ │ ├── amutex.c │ │ ├── amutex.h │ │ ├── anim.c │ │ ├── anim.h │ │ ├── art.c │ │ ├── art.h │ │ ├── artload.c │ │ ├── artload.h │ │ ├── automap.c │ │ ├── automap.h │ │ ├── bmpdlog.c │ │ ├── bmpdlog.h │ │ ├── cache.c │ │ ├── cache.h │ │ ├── cd.c │ │ ├── cd.h │ │ ├── combat.c │ │ ├── combat.h │ │ ├── combat_defs.h │ │ ├── combatai.c │ │ ├── combatai.h │ │ ├── combatai_defs.h │ │ ├── config.c │ │ ├── config.h │ │ ├── counter.c │ │ ├── counter.h │ │ ├── credits.c │ │ ├── credits.h │ │ ├── critter.c │ │ ├── critter.h │ │ ├── cycle.c │ │ ├── cycle.h │ │ ├── diskspce.c │ │ ├── diskspce.h │ │ ├── display.c │ │ ├── display.h │ │ ├── editor.c │ │ ├── editor.h │ │ ├── elevator.c │ │ ├── elevator.h │ │ ├── endgame.c │ │ ├── endgame.h │ │ ├── ereg.c │ │ ├── ereg.h │ │ ├── fontmgr.c │ │ ├── fontmgr.h │ │ ├── game.c │ │ ├── game.h │ │ ├── game_vars.h │ │ ├── gconfig.c │ │ ├── gconfig.h │ │ ├── gdebug.c │ │ ├── gdebug.h │ │ ├── gdialog.c │ │ ├── gdialog.h │ │ ├── gmemory.c │ │ ├── gmemory.h │ │ ├── gmouse.c │ │ ├── gmouse.h │ │ ├── gmovie.c │ │ ├── gmovie.h │ │ ├── graphlib.c │ │ ├── graphlib.h │ │ ├── gsound.c │ │ ├── gsound.h │ │ ├── gz.c │ │ ├── gz.h │ │ ├── heap.c │ │ ├── heap.h │ │ ├── intface.c │ │ ├── intface.h │ │ ├── inventry.c │ │ ├── inventry.h │ │ ├── item.c │ │ ├── item.h │ │ ├── light.c │ │ ├── light.h │ │ ├── lip_sync.c │ │ ├── lip_sync.h │ │ ├── loadsave.c │ │ ├── loadsave.h │ │ ├── main.c │ │ ├── main.h │ │ ├── mainmenu.c │ │ ├── mainmenu.h │ │ ├── map.c │ │ ├── map.h │ │ ├── map_defs.h │ │ ├── message.c │ │ ├── message.h │ │ ├── moviefx.c │ │ ├── moviefx.h │ │ ├── object.c │ │ ├── object.h │ │ ├── object_types.h │ │ ├── options.c │ │ ├── options.h │ │ ├── palette.c │ │ ├── palette.h │ │ ├── party.c │ │ ├── party.h │ │ ├── perk.c │ │ ├── perk.h │ │ ├── perk_defs.h │ │ ├── pipboy.c │ │ ├── pipboy.h │ │ ├── protinst.c │ │ ├── protinst.h │ │ ├── proto.c │ │ ├── proto.h │ │ ├── proto_types.h │ │ ├── queue.c │ │ ├── queue.h │ │ ├── reaction.c │ │ ├── reaction.h │ │ ├── roll.c │ │ ├── roll.h │ │ ├── scripts.c │ │ ├── scripts.h │ │ ├── select.c │ │ ├── select.h │ │ ├── selfrun.c │ │ ├── selfrun.h │ │ ├── sfxcache.c │ │ ├── sfxcache.h │ │ ├── sfxlist.c │ │ ├── sfxlist.h │ │ ├── skill.c │ │ ├── skill.h │ │ ├── skill_defs.h │ │ ├── skilldex.c │ │ ├── skilldex.h │ │ ├── stat.c │ │ ├── stat.h │ │ ├── stat_defs.h │ │ ├── strparse.c │ │ ├── strparse.h │ │ ├── textobj.c │ │ ├── textobj.h │ │ ├── tile.c │ │ ├── tile.h │ │ ├── trait.c │ │ ├── trait.h │ │ ├── trait_defs.h │ │ ├── trap.c │ │ ├── trap.h │ │ ├── version.c │ │ ├── version.h │ │ ├── wordwrap.c │ │ ├── wordwrap.h │ │ ├── worldmap.c │ │ └── worldmap.h │ ├── int/ │ │ ├── audio.c │ │ ├── audio.h │ │ ├── audiof.c │ │ ├── audiof.h │ │ ├── datafile.c │ │ ├── datafile.h │ │ ├── dialog.c │ │ ├── dialog.h │ │ ├── export.c │ │ ├── export.h │ │ ├── intlib.c │ │ ├── intlib.h │ │ ├── intrpret.c │ │ ├── intrpret.h │ │ ├── memdbg.c │ │ ├── memdbg.h │ │ ├── mousemgr.c │ │ ├── mousemgr.h │ │ ├── movie.c │ │ ├── movie.h │ │ ├── nevs.c │ │ ├── nevs.h │ │ ├── pcx.c │ │ ├── pcx.h │ │ ├── region.c │ │ ├── region.h │ │ ├── share1.c │ │ ├── share1.h │ │ ├── sound.c │ │ ├── sound.h │ │ ├── support/ │ │ │ ├── intextra.c │ │ │ └── intextra.h │ │ ├── widget.c │ │ ├── widget.h │ │ ├── window.c │ │ └── window.h │ ├── memory_defs.h │ ├── mmx.c │ ├── mmx.h │ ├── movie_lib.c │ ├── movie_lib.h │ ├── plib/ │ │ ├── assoc/ │ │ │ ├── assoc.c │ │ │ └── assoc.h │ │ ├── color/ │ │ │ ├── color.c │ │ │ └── color.h │ │ ├── db/ │ │ │ ├── db.c │ │ │ └── db.h │ │ ├── gnw/ │ │ │ ├── button.c │ │ │ ├── button.h │ │ │ ├── debug.c │ │ │ ├── debug.h │ │ │ ├── doscmdln.c │ │ │ ├── doscmdln.h │ │ │ ├── dxinput.c │ │ │ ├── dxinput.h │ │ │ ├── gnw.c │ │ │ ├── gnw.h │ │ │ ├── gnw95dx.c │ │ │ ├── gnw95dx.h │ │ │ ├── gnw_types.h │ │ │ ├── grbuf.c │ │ │ ├── grbuf.h │ │ │ ├── input.c │ │ │ ├── input.h │ │ │ ├── intrface.c │ │ │ ├── intrface.h │ │ │ ├── kb.c │ │ │ ├── kb.h │ │ │ ├── memory.c │ │ │ ├── memory.h │ │ │ ├── mouse.c │ │ │ ├── mouse.h │ │ │ ├── rect.c │ │ │ ├── rect.h │ │ │ ├── svga.c │ │ │ ├── svga.h │ │ │ ├── svga_types.h │ │ │ ├── text.c │ │ │ ├── text.h │ │ │ ├── vcr.c │ │ │ ├── vcr.h │ │ │ ├── winmain.c │ │ │ └── winmain.h │ │ └── xfile/ │ │ ├── dfile.c │ │ ├── dfile.h │ │ ├── xfile.c │ │ ├── xfile.h │ │ ├── xsys_find.c │ │ └── xsys_find.h │ ├── sound_decoder.c │ └── sound_decoder.h └── third_party/ ├── fpattern/ │ ├── CMakeLists.txt │ ├── LICENSE │ └── README.md └── zlib/ ├── CMakeLists.txt ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: WebKit AllowShortIfStatementsOnASingleLine: WithoutElse ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_white_space = true ================================================ FILE: .gitattributes ================================================ # Force LF *.c text eol=lf *.h text eol=lf *.md text eol=lf *.json text eol=lf *.yml text eol=lf .clang-format text eol=lf .editorconfig text eol=lf .gitattributes text eol=lf .gitignore text eol=lf CMakeLists.txt text eol=lf LICENSE text eol=lf ================================================ FILE: .github/workflows/ci-build.yml ================================================ name: CI on: push: branches: - main pull_request: types: - opened - synchronize defaults: run: shell: bash jobs: build: runs-on: windows-2019 steps: - name: Clone uses: actions/checkout@v3 - name: Cache cmake build uses: actions/cache@v3 with: path: build key: windows-x86-cmake-v1 - name: Configure run: | cmake \ -B build \ -G "Visual Studio 16 2019" \ -A Win32 \ # EOL - name: Build run: | cmake \ --build build \ --config RelWithDebInfo \ # EOL - name: Upload uses: actions/upload-artifact@v3 with: name: fallout2-re path: build/RelWithDebInfo/fallout2-re.exe retention-days: 7 ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Dd]ebug-Remote/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.tlog *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Nuget personal access tokens and Credentials nuget.config # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json *.code-workspace # Local History for Visual Studio Code .history/ # Windows Installer files from build outputs *.cab *.msi *.msix *.msm *.msp # JetBrains Rider .idea/ *.sln.iml # CMake /out ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.13) set(EXECUTABLE_NAME fallout2-re) project(${EXECUTABLE_NAME}) add_executable(${EXECUTABLE_NAME} WIN32 "src/game/ability.c" "src/game/ability.h" "src/game/actions.c" "src/game/actions.h" "src/game/amutex.c" "src/game/amutex.h" "src/game/anim.c" "src/game/anim.h" "src/game/art.c" "src/game/art.h" "src/game/artload.c" "src/game/artload.h" "src/game/automap.c" "src/game/automap.h" "src/game/bmpdlog.c" "src/game/bmpdlog.h" "src/game/cache.c" "src/game/cache.h" "src/game/cd.c" "src/game/cd.h" "src/game/combat_defs.h" "src/game/combat.c" "src/game/combat.h" "src/game/combatai_defs.h" "src/game/combatai.c" "src/game/combatai.h" "src/game/config.c" "src/game/config.h" "src/game/counter.c" "src/game/counter.h" "src/game/credits.c" "src/game/credits.h" "src/game/critter.c" "src/game/critter.h" "src/game/cycle.c" "src/game/cycle.h" "src/game/diskspce.c" "src/game/diskspce.h" "src/game/display.c" "src/game/display.h" "src/game/editor.c" "src/game/editor.h" "src/game/elevator.c" "src/game/elevator.h" "src/game/endgame.c" "src/game/endgame.h" "src/game/ereg.c" "src/game/ereg.h" "src/game/fontmgr.c" "src/game/fontmgr.h" "src/game/game_vars.h" "src/game/game.c" "src/game/game.h" "src/game/gconfig.c" "src/game/gconfig.h" "src/game/gdebug.c" "src/game/gdebug.h" "src/game/gdialog.c" "src/game/gdialog.h" "src/game/gmemory.c" "src/game/gmemory.h" "src/game/gmouse.c" "src/game/gmouse.h" "src/game/gmovie.c" "src/game/gmovie.h" "src/game/graphlib.c" "src/game/graphlib.h" "src/game/gsound.c" "src/game/gsound.h" "src/game/gz.c" "src/game/gz.h" "src/game/heap.c" "src/game/heap.h" "src/game/intface.c" "src/game/intface.h" "src/game/inventry.c" "src/game/inventry.h" "src/game/item.c" "src/game/item.h" "src/game/light.c" "src/game/light.h" "src/game/lip_sync.c" "src/game/lip_sync.h" "src/game/loadsave.c" "src/game/loadsave.h" "src/game/main.c" "src/game/main.h" "src/game/mainmenu.c" "src/game/mainmenu.h" "src/game/map_defs.h" "src/game/map.c" "src/game/map.h" "src/game/message.c" "src/game/message.h" "src/game/moviefx.c" "src/game/moviefx.h" "src/game/object_types.h" "src/game/object.c" "src/game/object.h" "src/game/options.c" "src/game/options.h" "src/game/palette.c" "src/game/palette.h" "src/game/party.c" "src/game/party.h" "src/game/perk_defs.h" "src/game/perk.c" "src/game/perk.h" "src/game/pipboy.c" "src/game/pipboy.h" "src/game/protinst.c" "src/game/protinst.h" "src/game/proto_types.h" "src/game/proto.c" "src/game/proto.h" "src/game/queue.c" "src/game/queue.h" "src/game/reaction.c" "src/game/reaction.h" "src/game/roll.c" "src/game/roll.h" "src/game/scripts.c" "src/game/scripts.h" "src/game/select.c" "src/game/select.h" "src/game/selfrun.c" "src/game/selfrun.h" "src/game/sfxcache.c" "src/game/sfxcache.h" "src/game/sfxlist.c" "src/game/sfxlist.h" "src/game/skill_defs.h" "src/game/skill.c" "src/game/skill.h" "src/game/skilldex.c" "src/game/skilldex.h" "src/game/stat_defs.h" "src/game/stat.c" "src/game/stat.h" "src/game/strparse.c" "src/game/strparse.h" "src/game/textobj.c" "src/game/textobj.h" "src/game/tile.c" "src/game/tile.h" "src/game/trait_defs.h" "src/game/trait.c" "src/game/trait.h" "src/game/trap.c" "src/game/trap.h" "src/game/version.c" "src/game/version.h" "src/game/wordwrap.c" "src/game/wordwrap.h" "src/game/worldmap.c" "src/game/worldmap.h" "src/int/audio.c" "src/int/audio.h" "src/int/audiof.c" "src/int/audiof.h" "src/int/datafile.c" "src/int/datafile.h" "src/int/dialog.c" "src/int/dialog.h" "src/int/export.c" "src/int/export.h" "src/int/intlib.c" "src/int/intlib.h" "src/int/intrpret.c" "src/int/intrpret.h" "src/int/memdbg.c" "src/int/memdbg.h" "src/int/mousemgr.c" "src/int/mousemgr.h" "src/int/movie.c" "src/int/movie.h" "src/int/nevs.c" "src/int/nevs.h" "src/int/pcx.c" "src/int/pcx.h" "src/int/region.c" "src/int/region.h" "src/int/share1.c" "src/int/share1.h" "src/int/sound.c" "src/int/sound.h" "src/int/support/intextra.c" "src/int/support/intextra.h" "src/int/widget.c" "src/int/widget.h" "src/int/window.c" "src/int/window.h" "src/plib/assoc/assoc.c" "src/plib/assoc/assoc.h" "src/plib/color/color.c" "src/plib/color/color.h" "src/plib/db/db.c" "src/plib/db/db.h" "src/plib/gnw/button.c" "src/plib/gnw/button.h" "src/plib/gnw/debug.c" "src/plib/gnw/debug.h" "src/plib/gnw/doscmdln.c" "src/plib/gnw/doscmdln.h" "src/plib/gnw/dxinput.c" "src/plib/gnw/dxinput.h" "src/plib/gnw/gnw95dx.c" "src/plib/gnw/gnw95dx.h" "src/plib/gnw/grbuf.c" "src/plib/gnw/grbuf.h" "src/plib/gnw/input.c" "src/plib/gnw/input.h" "src/plib/gnw/gnw_types.h" "src/plib/gnw/gnw.c" "src/plib/gnw/gnw.h" "src/plib/gnw/intrface.c" "src/plib/gnw/intrface.h" "src/plib/gnw/kb.c" "src/plib/gnw/kb.h" "src/plib/gnw/memory.c" "src/plib/gnw/memory.h" "src/plib/gnw/mouse.c" "src/plib/gnw/mouse.h" "src/plib/gnw/rect.c" "src/plib/gnw/rect.h" "src/plib/gnw/svga_types.h" "src/plib/gnw/svga.c" "src/plib/gnw/svga.h" "src/plib/gnw/text.c" "src/plib/gnw/text.h" "src/plib/gnw/vcr.c" "src/plib/gnw/vcr.h" "src/plib/gnw/winmain.c" "src/plib/gnw/winmain.h" "src/plib/xfile/dfile.c" "src/plib/xfile/dfile.h" "src/plib/xfile/xfile.c" "src/plib/xfile/xfile.h" "src/plib/xfile/xsys_find.c" "src/plib/xfile/xsys_find.h" "src/memory_defs.h" "src/mmx.c" "src/mmx.h" "src/movie_lib.c" "src/movie_lib.h" "src/sound_decoder.c" "src/sound_decoder.h" ) target_include_directories(${EXECUTABLE_NAME} PUBLIC src) target_compile_definitions(${EXECUTABLE_NAME} PUBLIC _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_WARNINGS ) target_link_libraries(${EXECUTABLE_NAME} winmm ) add_subdirectory("third_party/fpattern") target_link_libraries(${EXECUTABLE_NAME} ${FPATTERN_LIBRARY}) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${FPATTERN_INCLUDE_DIR}) add_subdirectory("third_party/zlib") target_link_libraries(${EXECUTABLE_NAME} ${ZLIB_LIBRARIES}) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZLIB_INCLUDE_DIRS}) ================================================ FILE: CMakeSettings.json ================================================ { "configurations": [ { "name": "x86-Debug", "generator": "Visual Studio 16 2019", "configurationType": "Debug", "inheritEnvironments": [ "msvc_x86" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "" }, { "name": "x86-Release", "generator": "Visual Studio 16 2019", "configurationType": "Release", "inheritEnvironments": [ "msvc_x86" ], "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "" } ] } ================================================ FILE: LICENSE.md ================================================ # Sustainable Use License Version 1.0 ## Acceptance By using the software, you agree to all of the terms and conditions below. ## Copyright License The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below. ## Limitations You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. ## Patents The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company. ## Notices You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software. ## No Other Rights These terms do not imply any licenses other than those expressly granted in these terms. ## Termination If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently. ## No Liability As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. ## Definitions The "licensor" is the entity offering these terms. The "software" is the software the licensor makes available under these terms, including any portion of it. "You" refers to the individual or entity agreeing to these terms. "Your company" is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect. "Your license" is the license granted to you for the software under these terms. "Use" means anything you do with the software requiring your license. "Trademark" means trademarks, service marks, and similar rights. ================================================ FILE: README.md ================================================ # Fallout 2 Reference Edition In this repository you'll find reverse engineered source code for Fallout 2. As a player/gamer you're most likely interested in [Fallout 2 Community Edition](https://github.com/alexbatalov/fallout2-ce) which is based on this project. If you're a developer you might also want to check [Fallout Reference Edition](https://github.com/alexbatalov/fallout1-re) to see evolution of the engine. ## Goal The goal of this project is to restore original source code as close possible with all it's imperfections. In very many respects this goal can be considered achieved. ## Status There is a small number of functions which are not yet decompiled. These functions are not essential to gameplay, most of them are leftovers from Fallout 1, others are a part of larger APIs that were not fully utilized. Aside from these missing functions there is ongoing effort to update the codebase to C89 to make sure the game can be compiled with Watcom C compiler (which might be handy to achieve binary identical results). This tasks are low priority and probably will never be completed. ## Installation You must own the game to play. Purchase your copy on [GOG](https://www.gog.com/game/fallout_2) or [Steam](https://store.steampowered.com/app/38410). Download latest build or build from source. The `fallout2-re.exe` serves as a drop-in replacement for `fallout2.exe`. Copy it to your Fallout 2 directory and run. ## Legal The source code in this repository is produced by reverse engineering the original binary. There are couple of exceptions for reverse engineering under DMCA - documentation, interoperability, fair use. Documentation is needed to achieve interoperability. Running your legally purchased copy on modern Mac M1 for example (interoperability in action) constitutes fair use. Publishing this stuff to wide audience is questionable. Eventually it's up to Bethesda/Microsoft to takedown the project or leave it be. See [#29](https://github.com/alexbatalov/fallout2-re/issues/29) for discussion. ## License The source code is this repository is available under the [Sustainable Use License](LICENSE.md). ================================================ FILE: src/game/ability.c ================================================ #include "game/ability.h" #include #include "plib/gnw/memory.h" // 0x410010 int abil_init(Ability* ability, int initialCapacity) { ability->length = 0; ability->capacity = 0; ability->entries = NULL; if (initialCapacity != 0) { ability->entries = (AbilityData*)mem_malloc(sizeof(*ability->entries) * initialCapacity); if (ability->entries != NULL) { ability->length = 0; ability->capacity = initialCapacity; return 0; } } return -1; } // 0x410058 int abil_resize(Ability* ability, int capacity) { AbilityData* entries; if (capacity >= ability->length) { entries = (AbilityData*)mem_realloc(ability->entries, sizeof(*ability->entries) * capacity); if (entries != NULL) { ability->entries = entries; ability->capacity = capacity; return 0; } } return -1; } // 0x410094 int abil_free(Ability* ability) { if (ability->entries != NULL) { mem_free(ability->entries); } return 0; } // 0x4100A8 int abil_find(Ability* ability, int a2, int* indexPtr) { int right; int left; int mid; int cmp; int rc; if (ability->length == 0) { rc = -1; *indexPtr = 0; } else { right = ability->length - 1; left = 0; while (right >= left) { mid = (right + left) >> 1; if (a2 == ability->entries[mid].field_0) { cmp = 0; break; } if (a2 < ability->entries[mid].field_0) { right = mid - 1; cmp = -1; } else { left = mid + 1; cmp = 1; } } if (cmp == 0) { *indexPtr = mid; rc = 0; } else { if (cmp < 0) { *indexPtr = mid; } else { *indexPtr = mid + 1; } rc = -1; } } return rc; } // 0x410130 int abil_search(Ability* ability, int a2) { int index; if (abil_find(ability, a2, &index) == 0) { return index; } return -1; } // 0x410154 int abil_insert(Ability* ability, AbilityData* entry) { int index; if (abil_find(ability, entry->field_0, &index) == 0) { return -1; } if (ability->capacity == ability->length && abil_resize(ability, ability->capacity * 2) == -1) { return -1; } if (sizeof(*ability->entries) * (ability->length - index) != 0) { memmove((unsigned char*)ability->entries + sizeof(*ability->entries) * index + sizeof(*ability->entries), (unsigned char*)ability->entries + sizeof(*ability->entries) * index, sizeof(*ability->entries) * (ability->length - index)); } ability->entries[index].field_0 = entry->field_0; ability->entries[index].field_4 = entry->field_4; ability->entries[index].field_8 = entry->field_8; ability->length++; return 0; } // 0x41021C int abil_delete(Ability* ability, int a2) { int index; if (abil_find(ability, a2, &index) == -1) { return -1; } ability->length--; if (sizeof(*ability->entries) * (ability->length - index) != 0) { memmove((unsigned char*)ability->entries + sizeof(*ability->entries) * index, (unsigned char*)ability->entries + sizeof(*ability->entries) * index + sizeof(*ability->entries), sizeof(*ability->entries) * (ability->length - index)); } return 0; } // 0x41026C int abil_copy(Ability* dest, Ability* src) { int index; if (abil_init(dest, src->capacity) == -1) { return -1; } for (index = 0; index < src->length; index++) { if (abil_insert(dest, &(src->entries[index])) == -1) { return -1; } } return 0; } // 0x4102B0 int abil_load(File* stream, Ability* ability) { if (db_freadInt(stream, &(ability->length)) == -1 || db_freadInt(stream, &(ability->capacity)) == -1 || abil_read_ability_data(ability, stream) == -1) return -1; return 0; } // 0x4102EC int abil_read_ability_data(Ability* ability, File* stream) { int index; ability->entries = (AbilityData*)mem_malloc(sizeof(*ability->entries) * ability->capacity); if (ability->entries == NULL) { return -1; } for (index = 0; index < ability->length; index++) { if (db_freadInt(stream, &(ability->entries[index].field_0)) == -1 || db_freadInt(stream, &(ability->entries[index].field_4)) == -1 || db_freadInt(stream, &(ability->entries[index].field_8)) == -1) { return -1; } } return 0; } // 0x410378 int abil_save(File* stream, Ability* ability) { if (db_fwriteInt(stream, ability->length) == -1 || db_fwriteInt(stream, ability->capacity) == -1 || abil_write_ability_data(ability->length, ability->entries, stream) == -1) { return -1; } return 0; } // 0x4103B8 int abil_write_ability_data(int length, AbilityData* entries, File* stream) { int index; for (index = 0; index < length; index++) { if (db_fwriteInt(stream, entries[index].field_0) == -1 || db_fwriteInt(stream, entries[index].field_4) == -1 || db_fwriteInt(stream, entries[index].field_8) == -1) { return -1; } } return 0; } ================================================ FILE: src/game/ability.h ================================================ #ifndef FALLOUT_GAME_ABILITY_H_ #define FALLOUT_GAME_ABILITY_H_ #include "plib/db/db.h" typedef struct AbilityData { int field_0; int field_4; int field_8; } AbilityData; typedef struct Ability { int length; int capacity; AbilityData* entries; } Ability; int abil_init(Ability* ability, int initialCapacity); int abil_resize(Ability* ability, int capacity); int abil_free(Ability* ability); int abil_find(Ability* ability, int a2, int* indexPtr); int abil_search(Ability* ability, int a2); int abil_insert(Ability* ability, AbilityData* entry); int abil_delete(Ability* ability, int a2); int abil_copy(Ability* dest, Ability* src); int abil_load(File* stream, Ability* ability); int abil_read_ability_data(Ability* ability, File* stream); int abil_save(File* stream, Ability* ability); int abil_write_ability_data(int length, AbilityData* entries, File* stream); #endif /* FALLOUT_GAME_ABILITY_H_ */ ================================================ FILE: src/game/actions.c ================================================ #include "game/actions.h" #include #include #include "game/anim.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "game/config.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/protinst.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/textobj.h" #include "game/tile.h" #include "game/trait.h" #include "plib/gnw/svga.h" static int pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int anim, bool isFallingBack); static int check_death(Object* obj, int anim, int minViolenceLevel, bool isFallingBack); static int internal_destroy(Object* a1, Object* a2); static int show_death(Object* obj, int anim); static int action_melee(Attack* attack, int a2); static int action_ranged(Attack* attack, int a2); static int is_next_to(Object* a1, Object* a2); static int action_climb_ladder(Object* a1, Object* a2); static int report_explosion(Attack* attack, Object* a2); static int finished_explosion(Object* a1, Object* a2); static int compute_explosion_damage(int min, int max, Object* a3, int* a4); static int can_talk_to(Object* a1, Object* a2); static int talk_to(Object* a1, Object* a2); static int report_dmg(Attack* attack, Object* a2); static int compute_dmg_damage(int min, int max, Object* obj, int* a4, int damage_type); // 0x5106D0 static bool action_in_explode = false; // 0x5106D4 unsigned int rotation = 0; // 0x5106D8 int obj_fid = -1; // 0x5106DC int obj_pid_old = -1; // TODO: Strange location in debug build, check if it's static in |pick_death|. // // 0x5106E0 static int death_2[DAMAGE_TYPE_COUNT] = { ANIM_DANCING_AUTOFIRE, ANIM_SLICED_IN_HALF, ANIM_CHARRED_BODY, ANIM_CHARRED_BODY, ANIM_ELECTRIFY, ANIM_FALL_BACK, ANIM_BIG_HOLE, }; // TODO: Strange location in debug build, check if it's static in |pick_death|. // // 0x5106FC static int death_3[DAMAGE_TYPE_COUNT] = { ANIM_CHUNKS_OF_FLESH, ANIM_SLICED_IN_HALF, ANIM_FIRE_DANCE, ANIM_MELTED_TO_NOTHING, ANIM_ELECTRIFIED_TO_NOTHING, ANIM_FALL_BACK, ANIM_EXPLODED_TO_NOTHING, }; // NOTE: Unused. // // 0x410410 void switch_dude() { Object* critter; int gender; critter = pick_object(OBJ_TYPE_CRITTER, false); if (critter != NULL) { gender = critterGetStat(critter, STAT_GENDER); stat_set_base(obj_dude, STAT_GENDER, gender); obj_dude = critter; obj_fid = critter->fid; obj_pid_old = critter->pid; critter->pid = 0x1000000; } } // 0x410468 int action_knockback(Object* obj, int* anim, int maxDistance, int rotation, int delay) { if (critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) { return -1; } if (*anim == ANIM_FALL_FRONT) { int fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, *anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); if (!art_exists(fid)) { *anim = ANIM_FALL_BACK; } } int distance; int tile; for (distance = 1; distance <= maxDistance; distance++) { tile = tile_num_in_direction(obj->tile, rotation, distance); if (obj_blocking_at(obj, tile, obj->elevation) != NULL) { distance--; break; } } const char* soundEffectName = gsnd_build_character_sfx_name(obj, *anim, CHARACTER_SOUND_EFFECT_KNOCKDOWN); register_object_play_sfx(obj, soundEffectName, delay); // TODO: Check, probably step back because we've started with 1? distance--; if (distance <= 0) { tile = obj->tile; register_object_animate(obj, *anim, 0); } else { tile = tile_num_in_direction(obj->tile, rotation, distance); register_object_animate_and_move_straight(obj, tile, obj->elevation, *anim, 0); } return tile; } // 0x410568 int action_blood(Object* obj, int anim, int delay) { int violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violence_level); if (violence_level == VIOLENCE_LEVEL_NONE) { return anim; } int bloodyAnim; if (anim == ANIM_FALL_BACK) { bloodyAnim = ANIM_FALL_BACK_BLOOD; } else if (anim == ANIM_FALL_FRONT) { bloodyAnim = ANIM_FALL_FRONT_BLOOD; } else { return anim; } int fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, bloodyAnim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); if (art_exists(fid)) { register_object_animate(obj, bloodyAnim, delay); } else { bloodyAnim = anim; } return bloodyAnim; } // 0x41060C static int pick_death(Object* attacker, Object* defender, Object* weapon, int damage, int anim, bool isFallingBack) { int normalViolenceLevelDamageThreshold = 15; int maximumBloodViolenceLevelDamageThreshold = 45; int damageType = item_w_damage_type(attacker, weapon); if (weapon != NULL && weapon->pid == PROTO_ID_MOLOTOV_COCKTAIL) { normalViolenceLevelDamageThreshold = 5; maximumBloodViolenceLevelDamageThreshold = 15; damageType = DAMAGE_TYPE_FIRE; anim = ANIM_FIRE_SINGLE; } if (attacker == obj_dude && perkHasRank(attacker, PERK_PYROMANIAC) && damageType == DAMAGE_TYPE_FIRE) { normalViolenceLevelDamageThreshold = 1; maximumBloodViolenceLevelDamageThreshold = 1; } if (weapon != NULL && item_w_perk(weapon) == PERK_WEAPON_FLAMEBOY) { normalViolenceLevelDamageThreshold /= 3; maximumBloodViolenceLevelDamageThreshold /= 3; } int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel); if (critter_flag_check(defender->pid, CRITTER_SPECIAL_DEATH)) { return check_death(defender, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_NORMAL, isFallingBack); } bool hasBloodyMess = false; if (attacker == obj_dude && trait_level(TRAIT_BLOODY_MESS)) { hasBloodyMess = true; } // NOTE: Original code is slightly different. There are lots of jumps and // conditions. It's easier to set the default in advance, rather than catch // it with bunch of "else" statements. int deathAnim = ANIM_FALL_BACK; if ((anim == ANIM_THROW_PUNCH && damageType == DAMAGE_TYPE_NORMAL) || anim == ANIM_KICK_LEG || anim == ANIM_THRUST_ANIM || anim == ANIM_SWING_ANIM || (anim == ANIM_THROW_ANIM && damageType != DAMAGE_TYPE_EXPLOSION)) { if (violenceLevel == VIOLENCE_LEVEL_MAXIMUM_BLOOD && hasBloodyMess) { deathAnim = ANIM_BIG_HOLE; } } else { if (anim == ANIM_FIRE_SINGLE && damageType == DAMAGE_TYPE_NORMAL) { if (violenceLevel == VIOLENCE_LEVEL_MAXIMUM_BLOOD) { if (hasBloodyMess || maximumBloodViolenceLevelDamageThreshold <= damage) { deathAnim = ANIM_BIG_HOLE; } } } else { if (violenceLevel > VIOLENCE_LEVEL_MINIMAL && (hasBloodyMess || normalViolenceLevelDamageThreshold <= damage)) { if (violenceLevel > VIOLENCE_LEVEL_NORMAL && (hasBloodyMess || maximumBloodViolenceLevelDamageThreshold <= damage)) { deathAnim = death_3[damageType]; if (check_death(defender, deathAnim, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack) != deathAnim) { deathAnim = death_2[damageType]; } } else { deathAnim = death_2[damageType]; } } } } if (!isFallingBack && deathAnim == ANIM_FALL_BACK) { deathAnim = ANIM_FALL_FRONT; } return check_death(defender, deathAnim, VIOLENCE_LEVEL_NONE, isFallingBack); } // 0x410814 static int check_death(Object* obj, int anim, int minViolenceLevel, bool isFallingBack) { int fid; int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel); if (violenceLevel >= minViolenceLevel) { fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); if (art_exists(fid)) { return anim; } } if (isFallingBack) { return ANIM_FALL_BACK; } fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1); if (art_exists(fid)) { return ANIM_FALL_BACK; } return ANIM_FALL_FRONT; } // 0x4108C8 static int internal_destroy(Object* a1, Object* a2) { return obj_destroy(a2); } // TODO: Check very carefully, lots of conditions and jumps. // // 0x4108D0 void show_damage_to_object(Object* a1, int damage, int flags, Object* weapon, bool isFallingBack, int knockbackDistance, int knockbackRotation, int a8, Object* a9, int a10) { int anim; int fid; const char* sfx_name; if (critter_flag_check(a1->pid, CRITTER_NO_KNOCKBACK)) { knockbackDistance = 0; } anim = FID_ANIM_TYPE(a1->fid); if (!critter_is_prone(a1)) { if ((flags & DAM_DEAD) != 0) { fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0); if (fid == a9->fid) { anim = check_death(a1, ANIM_EXPLODED_TO_NOTHING, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack); } else if (a9->pid == PROTO_ID_0x20001EB) { anim = check_death(a1, ANIM_ELECTRIFIED_TO_NOTHING, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack); } else if (a9->fid == FID_0x20001F5) { anim = check_death(a1, a8, VIOLENCE_LEVEL_MAXIMUM_BLOOD, isFallingBack); } else { anim = pick_death(a9, a1, weapon, damage, a8, isFallingBack); } if (anim != ANIM_FIRE_DANCE) { if (knockbackDistance != 0 && (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK)) { action_knockback(a1, &anim, knockbackDistance, knockbackRotation, a10); anim = action_blood(a1, anim, -1); } else { sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_DIE); register_object_play_sfx(a1, sfx_name, a10); anim = pick_fall(a1, anim); register_object_animate(a1, anim, 0); if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) { anim = action_blood(a1, anim, -1); } } } else { fid = art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_FIRE_DANCE, (a1->fid & 0xF000) >> 12, a1->rotation + 1); if (art_exists(fid)) { sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, sfx_name, a10); register_object_animate(a1, anim, 0); int randomDistance = roll_random(2, 5); int randomRotation = roll_random(0, 5); while (randomDistance > 0) { int tile = tile_num_in_direction(a1->tile, randomRotation, randomDistance); Object* v35 = NULL; make_straight_path(a1, a1->tile, tile, NULL, &v35, 4); if (v35 == NULL) { register_object_turn_towards(a1, tile); register_object_move_straight_to_tile(a1, tile, a1->elevation, anim, 0); break; } randomDistance--; } } anim = ANIM_BURNED_TO_NOTHING; sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, sfx_name, -1); register_object_animate(a1, anim, 0); } } else { if ((flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) { anim = isFallingBack ? ANIM_FALL_BACK : ANIM_FALL_FRONT; sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, sfx_name, a10); if (knockbackDistance != 0) { action_knockback(a1, &anim, knockbackDistance, knockbackRotation, 0); } else { anim = pick_fall(a1, anim); register_object_animate(a1, anim, 0); } } else if ((flags & DAM_ON_FIRE) != 0 && art_exists(art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_FIRE_DANCE, (a1->fid & 0xF000) >> 12, a1->rotation + 1))) { register_object_animate(a1, ANIM_FIRE_DANCE, a10); fid = art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_STAND, (a1->fid & 0xF000) >> 12, a1->rotation + 1); register_object_change_fid(a1, fid, -1); } else { if (knockbackDistance != 0) { anim = isFallingBack ? ANIM_FALL_BACK : ANIM_FALL_FRONT; action_knockback(a1, &anim, knockbackDistance, knockbackRotation, a10); if (anim == ANIM_FALL_BACK) { register_object_animate(a1, ANIM_BACK_TO_STANDING, -1); } else { register_object_animate(a1, ANIM_PRONE_TO_STANDING, -1); } } else { if (isFallingBack || !art_exists(art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_HIT_FROM_BACK, (a1->fid & 0xF000) >> 12, a1->rotation + 1))) { anim = ANIM_HIT_FROM_FRONT; } else { anim = ANIM_HIT_FROM_BACK; } sfx_name = gsnd_build_character_sfx_name(a1, anim, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, sfx_name, a10); register_object_animate(a1, anim, 0); } } } } else { if ((flags & DAM_DEAD) != 0 && (a1->data.critter.combat.results & DAM_DEAD) == 0) { anim = action_blood(a1, anim, a10); } else { return; } } if (weapon != NULL) { if ((flags & DAM_EXPLODE) != 0) { register_object_must_call(a1, weapon, obj_drop, -1); fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0); register_object_change_fid(weapon, fid, 0); register_object_animate_and_hide(weapon, ANIM_STAND, 0); sfx_name = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, a1); register_object_play_sfx(weapon, sfx_name, 0); register_object_must_erase(weapon); } else if ((flags & DAM_DESTROY) != 0) { register_object_must_call(a1, weapon, internal_destroy, -1); } else if ((flags & DAM_DROP) != 0) { register_object_must_call(a1, weapon, obj_drop, -1); } } if ((flags & DAM_DEAD) != 0) { // TODO: Get rid of casts. register_object_must_call(a1, (void*)anim, (AnimationCallback*)show_death, -1); } } // 0x410E24 static int show_death(Object* obj, int anim) { Rect v7; Rect v8; int fid; obj_bound(obj, &v8); if (anim < 48 && anim > 63) { fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, obj->rotation + 1); if (obj_change_fid(obj, fid, &v7) == 0) { rect_min_bound(&v8, &v7, &v8); } if (obj_set_frame(obj, 0, &v7) == 0) { rect_min_bound(&v8, &v7, &v8); } } if (critter_flag_check(obj->pid, CRITTER_FLAT) == 0) { obj->flags |= OBJECT_NO_BLOCK; if (obj_toggle_flat(obj, &v7) == 0) { rect_min_bound(&v8, &v7, &v8); } } if (obj_turn_off_outline(obj, &v7) == 0) { rect_min_bound(&v8, &v7, &v8); } if (anim >= 30 && anim <= 31 && critter_flag_check(obj->pid, CRITTER_SPECIAL_DEATH) == 0 && critter_flag_check(obj->pid, CRITTER_NO_DROP) == 0) { item_drop_all(obj, obj->tile); } tile_refresh_rect(&v8, obj->elevation); return 0; } // NOTE: Unused. // // 0x410F48 int show_damage_target(Attack* attack) { int frontHit; if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) { // NOTE: Uninline. frontHit = is_hit_from_front(attack->attacker, attack->defender); register_begin(ANIMATION_REQUEST_RESERVED); register_priority(1); show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, frontHit, attack->defenderKnockback, tile_dir(attack->attacker->tile, attack->defender->tile), item_w_anim(attack->attacker, attack->hitMode), attack->attacker, 0); register_end(); } return 0; } // 0x410FEC int show_damage_extras(Attack* attack) { int v6; int v8; int v9; for (int index = 0; index < attack->extrasLength; index++) { Object* obj = attack->extras[index]; if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) { int delta = attack->attacker->rotation - obj->rotation; if (delta < 0) { delta = -delta; } v6 = delta != 0 && delta != 1 && delta != 5; register_begin(ANIMATION_REQUEST_RESERVED); register_priority(1); v8 = item_w_anim(attack->attacker, attack->hitMode); v9 = tile_dir(attack->attacker->tile, obj->tile); show_damage_to_object(obj, attack->extrasDamage[index], attack->extrasFlags[index], attack->weapon, v6, attack->extrasKnockback[index], v9, v8, attack->attacker, 0); register_end(); } } return 0; } // 0x4110AC void show_damage(Attack* attack, int a2, int a3) { int v5; int v14; int v17; int v15; v5 = a3; for (int index = 0; index < attack->extrasLength; index++) { Object* object = attack->extras[index]; if (FID_TYPE(object->fid) == OBJ_TYPE_CRITTER) { register_ping(2, v5); v5 = 0; } } if ((attack->attackerFlags & DAM_HIT) == 0) { if ((attack->attackerFlags & DAM_CRITICAL) != 0) { show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, a2, attack->attacker, -1); } else if ((attack->attackerFlags & DAM_BACKWASH) != 0) { show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, a2, attack->attacker, -1); } } else { if (attack->defender != NULL) { // TODO: Looks very similar to show_damage_extras. int delta = attack->defender->rotation - attack->attacker->rotation; if (delta < 0) { delta = -delta; } v15 = delta != 0 && delta != 1 && delta != 5; if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) { if (attack->attacker->fid == 33554933) { v14 = tile_dir(attack->attacker->tile, attack->defender->tile); show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, v15, attack->defenderKnockback, v14, a2, attack->attacker, a3); } else { v17 = item_w_anim(attack->attacker, attack->hitMode); v14 = tile_dir(attack->attacker->tile, attack->defender->tile); show_damage_to_object(attack->defender, attack->defenderDamage, attack->defenderFlags, attack->weapon, v15, attack->defenderKnockback, v14, v17, attack->attacker, a3); } } else { tile_dir(attack->attacker->tile, attack->defender->tile); item_w_anim(attack->attacker, attack->hitMode); } } if ((attack->attackerFlags & DAM_DUD) != 0) { show_damage_to_object(attack->attacker, attack->attackerDamage, attack->attackerFlags, attack->weapon, 1, 0, 0, a2, attack->attacker, -1); } } } // 0x411224 int action_attack(Attack* attack) { if (register_clear(attack->attacker) == -2) { return -1; } if (register_clear(attack->defender) == -2) { return -1; } for (int index = 0; index < attack->extrasLength; index++) { if (register_clear(attack->extras[index]) == -2) { return -1; } } int anim = item_w_anim(attack->attacker, attack->hitMode); if (anim < ANIM_FIRE_SINGLE && anim != ANIM_THROW_ANIM) { return action_melee(attack, anim); } else { return action_ranged(attack, anim); } } // 0x4112B4 static int action_melee(Attack* attack, int anim) { int fid; Art* art; CacheEntry* cache_entry; int v17; int v18; int delta; int flag; const char* sfx_name; char sfx_name_temp[16]; register_begin(ANIMATION_REQUEST_RESERVED); register_priority(1); fid = art_id(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1); art = art_ptr_lock(fid, &cache_entry); if (art != NULL) { v17 = art_frame_action_frame(art); } else { v17 = 0; } art_ptr_unlock(cache_entry); tile_num_in_direction(attack->attacker->tile, attack->attacker->rotation, 1); register_object_turn_towards(attack->attacker, attack->defender->tile); delta = attack->attacker->rotation - attack->defender->rotation; if (delta < 0) { delta = -delta; } flag = delta != 0 && delta != 1 && delta != 5; if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) { sfx_name = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_ATTACK, attack->weapon, attack->hitMode, attack->defender); } else { sfx_name = gsnd_build_character_sfx_name(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED); } strcpy(sfx_name_temp, sfx_name); combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_ATTACK, 0); if (attack->attackerFlags & 0x0300) { register_object_play_sfx(attack->attacker, sfx_name_temp, 0); if (anim != ANIM_THROW_PUNCH && anim != ANIM_KICK_LEG) { sfx_name = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, attack->weapon, attack->hitMode, attack->defender); } else { sfx_name = gsnd_build_character_sfx_name(attack->attacker, anim, CHARACTER_SOUND_EFFECT_CONTACT); } strcpy(sfx_name_temp, sfx_name); register_object_animate(attack->attacker, anim, 0); register_object_play_sfx(attack->attacker, sfx_name_temp, v17); show_damage(attack, anim, 0); } else { if (attack->defender->data.critter.combat.results & 0x03) { register_object_play_sfx(attack->attacker, sfx_name_temp, -1); register_object_animate(attack->attacker, anim, 0); } else { fid = art_id(OBJ_TYPE_CRITTER, attack->defender->fid & 0xFFF, ANIM_DODGE_ANIM, (attack->defender->fid & 0xF000) >> 12, attack->defender->rotation + 1); art = art_ptr_lock(fid, &cache_entry); if (art != NULL) { v18 = art_frame_action_frame(art); art_ptr_unlock(cache_entry); if (v18 <= v17) { register_object_play_sfx(attack->attacker, sfx_name_temp, -1); register_object_animate(attack->attacker, anim, 0); sfx_name = gsnd_build_character_sfx_name(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(attack->defender, sfx_name, v17 - v18); register_object_animate(attack->defender, ANIM_DODGE_ANIM, 0); } else { sfx_name = gsnd_build_character_sfx_name(attack->defender, ANIM_DODGE_ANIM, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(attack->defender, sfx_name, -1); register_object_animate(attack->defender, ANIM_DODGE_ANIM, 0); register_object_play_sfx(attack->attacker, sfx_name_temp, v18 - v17); register_object_animate(attack->attacker, anim, 0); } } } } if ((attack->attackerFlags & DAM_HIT) != 0) { if ((attack->defenderFlags & DAM_DEAD) == 0) { combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_HIT, -1); } } else { combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_MISS, -1); } if (register_end() == -1) { return -1; } show_damage_extras(attack); return 0; } // NOTE: Unused. // // 0x4115CC int throw_change_fid(Object* object, int fid) { Rect rect; debug_printf("\n[throw_change_fid!]: %d", fid); obj_change_fid(object, fid, &rect); tile_refresh_rect(&rect, map_elevation); return 0; } // 0x411600 static int action_ranged(Attack* attack, int anim) { Object* neighboors[6]; memset(neighboors, 0, sizeof(neighboors)); register_begin(ANIMATION_REQUEST_RESERVED); register_priority(1); Object* projectile = NULL; Object* v50 = NULL; int weaponFid = -1; Proto* weaponProto; Object* weapon = attack->weapon; proto_ptr(weapon->pid, &weaponProto); int fid = art_id(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, anim, (attack->attacker->fid & 0xF000) >> 12, attack->attacker->rotation + 1); CacheEntry* artHandle; Art* art = art_ptr_lock(fid, &artHandle); int actionFrame = (art != NULL) ? art_frame_action_frame(art) : 0; art_ptr_unlock(artHandle); item_w_range(attack->attacker, attack->hitMode); int damageType = item_w_damage_type(attack->attacker, attack->weapon); tile_num_in_direction(attack->attacker->tile, attack->attacker->rotation, 1); register_object_turn_towards(attack->attacker, attack->defender->tile); bool isGrenade = false; if (anim == ANIM_THROW_ANIM) { if (damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP) { isGrenade = true; } } else { register_object_animate(attack->attacker, ANIM_POINT, -1); } combatai_msg(attack->attacker, attack, AI_MESSAGE_TYPE_ATTACK, 0); const char* sfx; if (((attack->attacker->fid & 0xF000) >> 12) != 0) { sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_ATTACK, weapon, attack->hitMode, attack->defender); } else { sfx = gsnd_build_character_sfx_name(attack->attacker, anim, CHARACTER_SOUND_EFFECT_UNUSED); } register_object_play_sfx(attack->attacker, sfx, -1); register_object_animate(attack->attacker, anim, 0); if (anim != ANIM_FIRE_CONTINUOUS) { if ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0) { bool l56 = false; int projectilePid = item_w_proj_pid(weapon); Proto* projectileProto; if (proto_ptr(projectilePid, &projectileProto) != -1 && projectileProto->fid != -1) { if (anim == ANIM_THROW_ANIM) { projectile = weapon; weaponFid = weapon->fid; int weaponFlags = weapon->flags; int leftItemAction; int rightItemAction; intface_get_item_states(&leftItemAction, &rightItemAction); item_remove_mult(attack->attacker, weapon, 1); v50 = item_replace(attack->attacker, weapon, weaponFlags & OBJECT_IN_ANY_HAND); obj_change_fid(projectile, projectileProto->fid, NULL); cAIPrepWeaponItem(attack->attacker, weapon); if (attack->attacker == obj_dude) { if (v50 == NULL) { if ((weaponFlags & OBJECT_IN_LEFT_HAND) != 0) { leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } else if ((weaponFlags & OBJECT_IN_RIGHT_HAND) != 0) { rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } } intface_update_items(false, leftItemAction, rightItemAction); } obj_connect(weapon, attack->attacker->tile, attack->attacker->elevation, NULL); } else { obj_new(&projectile, projectileProto->fid, -1); } obj_turn_off(projectile, NULL); obj_set_light(projectile, 9, projectile->lightIntensity, NULL); int projectileOrigin = combat_bullet_start(attack->attacker, attack->defender); obj_move_to_tile(projectile, projectileOrigin, attack->attacker->elevation, NULL); int projectileRotation = tile_dir(attack->attacker->tile, attack->defender->tile); obj_set_rotation(projectile, projectileRotation, NULL); register_object_funset(projectile, OBJECT_HIDDEN, actionFrame); const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_AMMO_FLYING, weapon, attack->hitMode, attack->defender); register_object_play_sfx(projectile, sfx, 0); int v24; if ((attack->attackerFlags & DAM_HIT) != 0) { register_object_move_straight_to_tile(projectile, attack->defender->tile, attack->defender->elevation, ANIM_WALK, 0); actionFrame = make_straight_path(projectile, projectileOrigin, attack->defender->tile, NULL, NULL, 32) - 1; v24 = attack->defender->tile; } else { register_object_move_straight_to_tile(projectile, attack->tile, attack->defender->elevation, ANIM_WALK, 0); actionFrame = 0; v24 = attack->tile; } if (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION) { if ((attack->attackerFlags & DAM_DROP) == 0) { int explosionFrmId; if (isGrenade) { switch (damageType) { case DAMAGE_TYPE_EMP: explosionFrmId = 2; break; case DAMAGE_TYPE_PLASMA: explosionFrmId = 31; break; default: explosionFrmId = 29; break; } } else { explosionFrmId = 10; } if (isGrenade) { register_object_change_fid(projectile, weaponFid, -1); } int explosionFid = art_id(OBJ_TYPE_MISC, explosionFrmId, 0, 0, 0); register_object_change_fid(projectile, explosionFid, -1); const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender); register_object_play_sfx(projectile, sfx, 0); register_object_animate_and_hide(projectile, ANIM_STAND, 0); for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { if (obj_new(&(neighboors[rotation]), explosionFid, -1) != -1) { obj_turn_off(neighboors[rotation], NULL); int v31 = tile_num_in_direction(v24, rotation, 1); obj_move_to_tile(neighboors[rotation], v31, projectile->elevation, NULL); int delay; if (rotation != ROTATION_NE) { delay = 0; } else { if (damageType == DAMAGE_TYPE_PLASMA) { delay = 4; } else { delay = 2; } } register_object_funset(neighboors[rotation], OBJECT_HIDDEN, delay); register_object_animate_and_hide(neighboors[rotation], ANIM_STAND, 0); } } l56 = true; } } else { if (anim != ANIM_THROW_ANIM) { register_object_must_erase(projectile); } } if (!l56) { const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_HIT, weapon, attack->hitMode, attack->defender); register_object_play_sfx(weapon, sfx, actionFrame); } actionFrame = 0; } else { if ((attack->attackerFlags & DAM_HIT) == 0) { Object* defender = attack->defender; if ((defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) == 0) { register_object_animate(defender, ANIM_DODGE_ANIM, actionFrame); l56 = true; } } } } } show_damage(attack, anim, actionFrame); if ((attack->attackerFlags & DAM_HIT) == 0) { combatai_msg(attack->defender, attack, AI_MESSAGE_TYPE_MISS, -1); } else { if ((attack->defenderFlags & DAM_DEAD) == 0) { combatai_msg(attack->defender, attack, AI_MESSAGE_TYPE_HIT, -1); } } if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION)) { register_object_must_erase(projectile); } else if (anim == ANIM_THROW_ANIM && projectile != NULL) { register_object_change_fid(projectile, weaponFid, -1); } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { if (neighboors[rotation] != NULL) { register_object_must_erase(neighboors[rotation]); } } if ((attack->attackerFlags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_DEAD)) == 0) { if (anim == ANIM_THROW_ANIM) { bool l9 = false; if (v50 != NULL) { int v38 = item_w_anim_code(v50); if (v38 != 0) { register_object_take_out(attack->attacker, v38, -1); l9 = true; } } if (!l9) { int fid = art_id(OBJ_TYPE_CRITTER, attack->attacker->fid & 0xFFF, ANIM_STAND, 0, attack->attacker->rotation + 1); register_object_change_fid(attack->attacker, fid, -1); } } else { register_object_animate(attack->attacker, ANIM_UNPOINT, -1); } } if (register_end() == -1) { debug_printf("Something went wrong with a ranged attack sequence!\n"); if (projectile != NULL && (isGrenade || damageType == DAMAGE_TYPE_EXPLOSION || anim != ANIM_THROW_ANIM)) { obj_erase_object(projectile, NULL); } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { if (neighboors[rotation] != NULL) { obj_erase_object(neighboors[rotation], NULL); } } return -1; } show_damage_extras(attack); return 0; } // 0x411D68 static int is_next_to(Object* a1, Object* a2) { if (obj_dist(a1, a2) > 1) { if (a2 == obj_dude) { MessageListItem messageListItem; // You cannot get there. messageListItem.num = 2000; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } return -1; } return 0; } // 0x411DB4 static int action_climb_ladder(Object* a1, Object* a2) { if (a1 == obj_dude) { int anim = FID_ANIM_TYPE(obj_dude->fid); if (anim == ANIM_WALK || anim == ANIM_RUNNING) { register_clear(obj_dude); } } int animationRequestOptions; int actionPoints; if (isInCombat()) { animationRequestOptions = ANIMATION_REQUEST_RESERVED; actionPoints = a1->data.critter.combat.ap; } else { animationRequestOptions = ANIMATION_REQUEST_UNRESERVED; actionPoints = -1; } if (a1 == obj_dude) { animationRequestOptions = ANIMATION_REQUEST_RESERVED; } animationRequestOptions |= ANIMATION_REQUEST_NO_STAND; register_begin(animationRequestOptions); int tile = tile_num_in_direction(a2->tile, ROTATION_SE, 1); if (actionPoints != -1 || obj_dist(a1, a2) < 5) { register_object_move_to_tile(a1, tile, a2->elevation, actionPoints, 0); } else { register_object_run_to_tile(a1, tile, a2->elevation, actionPoints, 0); } register_object_must_call(a1, a2, is_next_to, -1); register_object_turn_towards(a1, a2->tile); register_object_must_call(a1, a2, check_scenery_ap_cost, -1); int weaponAnimationCode = (a1->fid & 0xF000) >> 12; if (weaponAnimationCode != 0) { const char* puttingAwaySfx = gsnd_build_character_sfx_name(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, puttingAwaySfx, -1); register_object_animate(a1, ANIM_PUT_AWAY, 0); } const char* climbingSfx = gsnd_build_character_sfx_name(a1, ANIM_CLIMB_LADDER, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, climbingSfx, -1); register_object_animate(a1, ANIM_CLIMB_LADDER, 0); register_object_call(a1, a2, obj_use, -1); if (weaponAnimationCode != 0) { register_object_take_out(a1, weaponAnimationCode, -1); } return register_end(); } // 0x411F2C int a_use_obj(Object* a1, Object* a2, Object* a3) { Proto* proto = NULL; int type = FID_TYPE(a2->fid); int sceneryType = -1; if (type == OBJ_TYPE_SCENERY) { if (proto_ptr(a2->pid, &proto) == -1) { return -1; } sceneryType = proto->scenery.type; } if (sceneryType != SCENERY_TYPE_LADDER_UP || a3 != NULL) { if (a1 == obj_dude) { int anim = FID_ANIM_TYPE(obj_dude->fid); if (anim == ANIM_WALK || anim == ANIM_RUNNING) { register_clear(obj_dude); } } int animationRequestOptions; int actionPoints; if (isInCombat()) { animationRequestOptions = ANIMATION_REQUEST_RESERVED; actionPoints = a1->data.critter.combat.ap; } else { animationRequestOptions = ANIMATION_REQUEST_UNRESERVED; actionPoints = -1; } if (a1 == obj_dude) { animationRequestOptions = ANIMATION_REQUEST_RESERVED; } register_begin(animationRequestOptions); if (actionPoints != -1 || obj_dist(a1, a2) < 5) { register_object_move_to_object(a1, a2, actionPoints, 0); } else { register_object_run_to_object(a1, a2, -1, 0); } register_object_must_call(a1, a2, is_next_to, -1); if (a3 == NULL) { register_object_call(a1, a2, check_scenery_ap_cost, -1); } int a2a = (a1->fid & 0xF000) >> 12; if (a2a != 0) { const char* sfx = gsnd_build_character_sfx_name(a1, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(a1, sfx, -1); register_object_animate(a1, ANIM_PUT_AWAY, 0); } int anim; int objectType = FID_TYPE(a2->fid); if (objectType == OBJ_TYPE_CRITTER && critter_is_prone(a2)) { anim = ANIM_MAGIC_HANDS_GROUND; } else if (objectType == OBJ_TYPE_SCENERY && (proto->scenery.extendedFlags & 0x01) != 0) { anim = ANIM_MAGIC_HANDS_GROUND; } else { anim = ANIM_MAGIC_HANDS_MIDDLE; } if (sceneryType != SCENERY_TYPE_STAIRS && a3 == NULL) { register_object_animate(a1, anim, -1); } if (a3 != NULL) { // TODO: Get rid of cast. register_object_call3(a1, a2, a3, obj_use_item_on, -1); } else { register_object_call(a1, a2, obj_use, -1); } if (a2a != 0) { register_object_take_out(a1, a2a, -1); } return register_end(); } return action_climb_ladder(a1, a2); } // 0x411F2C int action_use_an_item_on_object(Object* a1, Object* a2, Object* a3) { return a_use_obj(a1, a2, a3); } // 0x412114 int action_use_an_object(Object* a1, Object* a2) { return a_use_obj(a1, a2, NULL); } // NOTE: Unused. // // 0x412120 int get_an_object(Object* item) { return action_get_an_object(obj_dude, item); } // 0x412134 int action_get_an_object(Object* critter, Object* item) { if (FID_TYPE(item->fid) != OBJ_TYPE_ITEM) { return -1; } if (critter == obj_dude) { int animationCode = FID_ANIM_TYPE(obj_dude->fid); if (animationCode == ANIM_WALK || animationCode == ANIM_RUNNING) { register_clear(obj_dude); } } if (isInCombat()) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_object(critter, item, critter->data.critter.combat.ap, 0); } else { register_begin(critter == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED); if (obj_dist(critter, item) >= 5) { register_object_run_to_object(critter, item, -1, 0); } else { register_object_move_to_object(critter, item, -1, 0); } } register_object_must_call(critter, item, is_next_to, -1); register_object_call(critter, item, check_scenery_ap_cost, -1); Proto* itemProto; proto_ptr(item->pid, &itemProto); if (itemProto->item.type != ITEM_TYPE_CONTAINER || proto_action_can_pickup(item->pid)) { register_object_animate(critter, ANIM_MAGIC_HANDS_GROUND, 0); int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_MAGIC_HANDS_GROUND, (critter->fid & 0xF000) >> 12, critter->rotation + 1); int actionFrame; CacheEntry* cacheEntry; Art* art = art_ptr_lock(fid, &cacheEntry); if (art != NULL) { actionFrame = art_frame_action_frame(art); } else { actionFrame = -1; } char sfx[16]; if (art_get_base_name(FID_TYPE(item->fid), item->fid & 0xFFF, sfx) == 0) { // NOTE: looks like they copy sfx one more time, what for? register_object_play_sfx(item, sfx, actionFrame); } register_object_call(critter, item, obj_pickup, actionFrame); } else { int weaponAnimationCode = (critter->fid & 0xF000) >> 12; if (weaponAnimationCode != 0) { const char* sfx = gsnd_build_character_sfx_name(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(critter, sfx, -1); register_object_animate(critter, ANIM_PUT_AWAY, -1); } // ground vs middle animation int anim = (itemProto->item.data.container.openFlags & 0x01) == 0 ? ANIM_MAGIC_HANDS_MIDDLE : ANIM_MAGIC_HANDS_GROUND; register_object_animate(critter, anim, 0); int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, 0, critter->rotation + 1); int actionFrame; CacheEntry* cacheEntry; Art* art = art_ptr_lock(fid, &cacheEntry); if (art == NULL) { actionFrame = art_frame_action_frame(art); art_ptr_unlock(cacheEntry); } else { actionFrame = -1; } if (item->frame != 1) { register_object_call(critter, item, obj_use_container, actionFrame); } if (weaponAnimationCode != 0) { register_object_take_out(critter, weaponAnimationCode, -1); } if (item->frame == 0 || item->frame == 1) { register_object_call(critter, item, scripts_request_loot_container, -1); } } return register_end(); } // TODO: Looks like the name is a little misleading, container can only be a // critter, which is enforced by this function as well as at the call sites. // Used to loot corpses, so probably should be something like actionLootCorpse. // Check if it can be called with a living critter. // // 0x4123E8 int action_loot_container(Object* critter, Object* container) { if (FID_TYPE(container->fid) != OBJ_TYPE_CRITTER) { return -1; } if (critter == obj_dude) { int anim = FID_ANIM_TYPE(obj_dude->fid); if (anim == ANIM_WALK || anim == ANIM_RUNNING) { register_clear(obj_dude); } } if (isInCombat()) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_object(critter, container, critter->data.critter.combat.ap, 0); } else { register_begin(critter == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED); if (obj_dist(critter, container) < 5) { register_object_move_to_object(critter, container, -1, 0); } else { register_object_run_to_object(critter, container, -1, 0); } } register_object_must_call(critter, container, is_next_to, -1); register_object_call(critter, container, check_scenery_ap_cost, -1); register_object_call(critter, container, scripts_request_loot_container, -1); return register_end(); } // 0x4124E0 int action_skill_use(int skill) { if (skill == SKILL_SNEAK) { register_clear(obj_dude); pc_flag_toggle(DUDE_STATE_SNEAKING); return 0; } return -1; } // NOTE: Inlined. // // 0x412500 int action_use_skill_in_combat_error(Object* critter) { MessageListItem messageListItem; if (critter == obj_dude) { messageListItem.num = 902; if (message_search(&proto_main_msg_file, &messageListItem) == 1) { display_print(messageListItem.text); } } return -1; } // skill_use // 0x41255C int action_use_skill_on(Object* a1, Object* a2, int skill) { switch (skill) { case SKILL_FIRST_AID: case SKILL_DOCTOR: if (isInCombat()) { // NOTE: Uninline. return action_use_skill_in_combat_error(a1); } if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) { return -1; } break; case SKILL_LOCKPICK: if (isInCombat()) { // NOTE: Uninline. return action_use_skill_in_combat_error(a1); } if (PID_TYPE(a2->pid) != OBJ_TYPE_ITEM && PID_TYPE(a2->pid) != OBJ_TYPE_SCENERY) { return -1; } break; case SKILL_STEAL: if (isInCombat()) { // NOTE: Uninline. return action_use_skill_in_combat_error(a1); } if (PID_TYPE(a2->pid) != OBJ_TYPE_ITEM && PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) { return -1; } if (a2 == a1) { return -1; } break; case SKILL_TRAPS: if (isInCombat()) { // NOTE: Uninline. return action_use_skill_in_combat_error(a1); } if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER) { return -1; } break; case SKILL_SCIENCE: case SKILL_REPAIR: if (isInCombat()) { // NOTE: Uninline. return action_use_skill_in_combat_error(a1); } if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) { break; } if (critterGetKillType(a2) == KILL_TYPE_ROBOT) { break; } if (critterGetKillType(a2) == KILL_TYPE_BRAHMIN && skill == SKILL_SCIENCE) { break; } return -1; case SKILL_SNEAK: pc_flag_toggle(DUDE_STATE_SNEAKING); return 0; default: debug_printf("\nskill_use: invalid skill used."); } // Performer is either dude, or party member who's best at the specified // skill in entire party, and this skill is his/her own best. Object* performer = obj_dude; if (a1 == obj_dude) { Object* partyMember = partyMemberWithHighestSkill(skill); if (partyMember == obj_dude) { partyMember = NULL; } // Only dude can perform stealing. if (skill == SKILL_STEAL) { partyMember = NULL; } if (partyMember != NULL) { if (partyMemberSkill(partyMember) != skill) { partyMember = NULL; } } if (partyMember != NULL) { performer = partyMember; int anim = FID_ANIM_TYPE(partyMember->fid); if (anim != ANIM_WALK && anim != ANIM_RUNNING) { if (anim != ANIM_STAND) { performer = obj_dude; partyMember = NULL; } } else { register_clear(partyMember); } } if (partyMember != NULL) { bool v32 = false; if (obj_dist(obj_dude, a2) <= 1) { v32 = true; } char* msg = skillGetPartyMemberString(partyMember, v32); Rect rect; if (text_object_create(partyMember, msg, 101, colorTable[32747], colorTable[0], &rect) == 0) { tile_refresh_rect(&rect, map_elevation); } if (v32) { performer = obj_dude; partyMember = NULL; } } if (partyMember == NULL) { int anim = FID_ANIM_TYPE(performer->fid); if (anim == ANIM_WALK || anim == ANIM_RUNNING) { register_clear(performer); } } } if (isInCombat()) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_object(performer, a2, performer->data.critter.combat.ap, 0); } else { register_begin(a1 == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED); if (a2 != obj_dude) { if (obj_dist(performer, a2) >= 5) { register_object_run_to_object(performer, a2, -1, 0); } else { register_object_move_to_object(performer, a2, -1, 0); } } } register_object_must_call(performer, a2, is_next_to, -1); int anim = (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER && critter_is_prone(a2)) ? ANIM_MAGIC_HANDS_GROUND : ANIM_MAGIC_HANDS_MIDDLE; int fid = art_id(OBJ_TYPE_CRITTER, performer->fid & 0xFFF, anim, 0, performer->rotation + 1); CacheEntry* artHandle; Art* art = art_ptr_lock(fid, &artHandle); if (art != NULL) { art_frame_action_frame(art); art_ptr_unlock(artHandle); } register_object_animate(performer, anim, -1); // TODO: Get rid of casts. register_object_call3(performer, a2, (void*)skill, (AnimationCallback3*)obj_use_skill_on, -1); return register_end(); } // NOTE: Unused. // // 0x4129CC Object* pick_object(int objectType, bool a2) { Object* foundObject; int mouseEvent; int keyCode; foundObject = NULL; do { get_input(); } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); gmouse_set_cursor(MOUSE_CURSOR_PLUS); gmouse_3d_off(); do { if (get_input() == -2) { mouseEvent = mouse_get_buttons(); if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { keyCode = 0; foundObject = object_under_mouse(objectType, a2, map_elevation); break; } if ((mouseEvent & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { keyCode = KEY_ESCAPE; break; } } } while (game_user_wants_to_quit == 0); gmouse_set_cursor(MOUSE_CURSOR_ARROW); gmouse_3d_on(); if (keyCode == KEY_ESCAPE) { return NULL; } return foundObject; } // NOTE: Unused. // // 0x412A54 int pick_hex() { int elevation; int inputEvent; int tile; Rect rect; elevation = map_elevation; while (1) { inputEvent = get_input(); if (inputEvent == -2) { break; } if (inputEvent == KEY_CTRL_ARROW_RIGHT) { rotation++; if (rotation > 5) { rotation = 0; } obj_set_rotation(obj_mouse, rotation, &rect); tile_refresh_rect(&rect, obj_mouse->elevation); } if (inputEvent == KEY_CTRL_ARROW_LEFT) { rotation--; if (rotation == -1) { rotation = 5; } obj_set_rotation(obj_mouse, rotation, &rect); tile_refresh_rect(&rect, obj_mouse->elevation); } if (inputEvent == KEY_PAGE_UP || inputEvent == KEY_PAGE_DOWN) { if (inputEvent == KEY_PAGE_UP) { map_set_elevation(map_elevation + 1); } else { map_set_elevation(map_elevation - 1); } rect.ulx = 30; rect.uly = 62; rect.lrx = 50; rect.lry = 88; } if (game_user_wants_to_quit != 0) { return -1; } } if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) == 0) { return -1; } if (!mouse_click_in(0, 0, scr_size.lrx - scr_size.ulx, scr_size.lry - scr_size.uly - 100)) { return -1; } mouse_get_position(&(rect.ulx), &(rect.uly)); tile = tile_num(rect.ulx, rect.uly, elevation); if (tile == -1) { return -1; } return tile; } // 0x412BC4 bool is_hit_from_front(Object* a1, Object* a2) { int diff = a1->rotation - a2->rotation; if (diff < 0) { diff = -diff; } return diff != 0 && diff != 1 && diff != 5; } // 0x412BEC bool can_see(Object* a1, Object* a2) { int diff; diff = a1->rotation - tile_dir(a1->tile, a2->tile); if (diff < 0) { diff = -diff; } return diff == 0 || diff == 1 || diff == 5; } // looks like it tries to change fall animation depending on object's current rotation // 0x412C1C int pick_fall(Object* obj, int anim) { int i; int rotation; int tile_num; int fid; if (anim == ANIM_FALL_FRONT) { rotation = obj->rotation; for (i = 1; i < 3; i++) { tile_num = tile_num_in_direction(obj->tile, rotation, i); if (obj_blocking_at(obj, tile_num, obj->elevation) != NULL) { anim = ANIM_FALL_BACK; break; } } } else if (anim == ANIM_FALL_BACK) { rotation = (obj->rotation + 3) % ROTATION_COUNT; for (i = 1; i < 3; i++) { tile_num = tile_num_in_direction(obj->tile, rotation, i); if (obj_blocking_at(obj, tile_num, obj->elevation) != NULL) { anim = ANIM_FALL_FRONT; break; } } } if (anim == ANIM_FALL_FRONT) { fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, ANIM_FALL_FRONT, (obj->fid & 0xF000) >> 12, obj->rotation + 1); if (!art_exists(fid)) { anim = ANIM_FALL_BACK; } } return anim; } // 0x412CE4 bool action_explode_running() { return action_in_explode; } // action_explode // 0x412CF4 int action_explode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6) { if (a6 && action_in_explode) { return -2; } Attack* attack = (Attack*)mem_malloc(sizeof(*attack)); if (attack == NULL) { return -1; } Object* explosion; int fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0); if (obj_new(&explosion, fid, -1) == -1) { mem_free(attack); return -1; } obj_turn_off(explosion, NULL); explosion->flags |= OBJECT_TEMPORARY; obj_move_to_tile(explosion, tile, elevation, NULL); Object* adjacentExplosions[ROTATION_COUNT]; for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int fid = art_id(OBJ_TYPE_MISC, 10, 0, 0, 0); if (obj_new(&(adjacentExplosions[rotation]), fid, -1) == -1) { while (--rotation >= 0) { obj_erase_object(adjacentExplosions[rotation], NULL); } obj_erase_object(explosion, NULL); mem_free(attack); return -1; } obj_turn_off(adjacentExplosions[rotation], NULL); adjacentExplosions[rotation]->flags |= OBJECT_TEMPORARY; int adjacentTile = tile_num_in_direction(tile, rotation, 1); obj_move_to_tile(adjacentExplosions[rotation], adjacentTile, elevation, NULL); } Object* critter = obj_blocking_at(NULL, tile, elevation); if (critter != NULL) { if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER || (critter->data.critter.combat.results & DAM_DEAD) != 0) { critter = NULL; } } combat_ctd_init(attack, explosion, critter, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); attack->tile = tile; attack->attackerFlags = DAM_HIT; game_ui_disable(1); if (critter != NULL) { if (register_clear(critter) == -2) { debug_printf("Cannot clear target's animation for action_explode!\n"); } attack->defenderDamage = compute_explosion_damage(minDamage, maxDamage, critter, &(attack->defenderKnockback)); } compute_explosion_on_extras(attack, 0, 0, 1); for (int index = 0; index < attack->extrasLength; index++) { Object* critter = attack->extras[index]; if (register_clear(critter) == -2) { debug_printf("Cannot clear extra's animation for action_explode!\n"); } attack->extrasDamage[index] = compute_explosion_damage(minDamage, maxDamage, critter, &(attack->extrasKnockback[index])); } death_checks(attack); if (a6) { action_in_explode = true; register_begin(ANIMATION_REQUEST_RESERVED); register_priority(1); register_object_play_sfx(explosion, "whn1xxx1", 0); register_object_funset(explosion, OBJECT_HIDDEN, 0); register_object_animate_and_hide(explosion, ANIM_STAND, 0); show_damage(attack, 0, 1); for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { register_object_funset(adjacentExplosions[rotation], OBJECT_HIDDEN, 0); register_object_animate_and_hide(adjacentExplosions[rotation], ANIM_STAND, 0); } register_object_must_call(explosion, 0, combat_explode_scenery, -1); register_object_must_erase(explosion); for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { register_object_must_erase(adjacentExplosions[rotation]); } register_object_must_call(attack, a5, report_explosion, -1); register_object_must_call(NULL, NULL, finished_explosion, -1); if (register_end() == -1) { action_in_explode = false; obj_erase_object(explosion, NULL); for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { obj_erase_object(adjacentExplosions[rotation], NULL); } mem_free(attack); game_ui_enable(); return -1; } show_damage_extras(attack); } else { if (critter != NULL) { if ((attack->defenderFlags & DAM_DEAD) != 0) { critter_kill(critter, -1, false); } } for (int index = 0; index < attack->extrasLength; index++) { if ((attack->extrasFlags[index] & DAM_DEAD) != 0) { critter_kill(attack->extras[index], -1, false); } } report_explosion(attack, a5); combat_explode_scenery(explosion, NULL); obj_erase_object(explosion, NULL); for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { obj_erase_object(adjacentExplosions[rotation], NULL); } } return 0; } // 0x413144 static int report_explosion(Attack* attack, Object* a2) { bool mainTargetWasDead; if (attack->defender != NULL) { mainTargetWasDead = (attack->defender->data.critter.combat.results & DAM_DEAD) != 0; } else { mainTargetWasDead = false; } bool extrasWasDead[6]; for (int index = 0; index < attack->extrasLength; index++) { extrasWasDead[index] = (attack->extras[index]->data.critter.combat.results & DAM_DEAD) != 0; } death_checks(attack); combat_display(attack); apply_damage(attack, false); Object* anyDefender = NULL; int xp = 0; if (a2 != NULL) { if (attack->defender != NULL && attack->defender != a2) { if ((attack->defender->data.critter.combat.results & DAM_DEAD) != 0) { if (a2 == obj_dude && !mainTargetWasDead) { xp += critter_kill_exps(attack->defender); } } else { critter_set_who_hit_me(attack->defender, a2); anyDefender = attack->defender; } } for (int index = 0; index < attack->extrasLength; index++) { Object* critter = attack->extras[index]; if (critter != a2) { if ((critter->data.critter.combat.results & DAM_DEAD) != 0) { if (a2 == obj_dude && !extrasWasDead[index]) { xp += critter_kill_exps(critter); } } else { critter_set_who_hit_me(critter, a2); if (anyDefender == NULL) { anyDefender = critter; } } } } if (anyDefender != NULL) { if (!isInCombat()) { STRUCT_664980 combat; combat.attacker = anyDefender; combat.defender = a2; combat.actionPointsBonus = 0; combat.accuracyBonus = 0; combat.damageBonus = 0; combat.minDamage = 0; combat.maxDamage = INT_MAX; combat.field_1C = 0; scripts_request_combat(&combat); } } } mem_free(attack); game_ui_enable(); if (a2 == obj_dude) { combat_give_exps(xp); } return 0; } // 0x4132C0 static int finished_explosion(Object* a1, Object* a2) { action_in_explode = false; return 0; } // calculate explosion damage applying threshold and resistances // 0x4132CC static int compute_explosion_damage(int min, int max, Object* a3, int* a4) { int v5 = roll_random(min, max); int v7 = v5 - critterGetStat(a3, STAT_DAMAGE_THRESHOLD_EXPLOSION); if (v7 > 0) { v7 -= critterGetStat(a3, STAT_DAMAGE_RESISTANCE_EXPLOSION) * v7 / 100; } if (v7 < 0) { v7 = 0; } if (a4 != NULL) { if ((a3->flags & OBJECT_MULTIHEX) == 0) { *a4 = v7 / 10; } } return v7; } // 0x413330 int action_talk_to(Object* a1, Object* a2) { if (a1 != obj_dude) { return -1; } if (FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) { return -1; } int anim = FID_ANIM_TYPE(obj_dude->fid); if (anim == ANIM_WALK || anim == ANIM_RUNNING) { register_clear(obj_dude); } if (isInCombat()) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_object(a1, a2, a1->data.critter.combat.ap, 0); } else { register_begin(a1 == obj_dude ? ANIMATION_REQUEST_RESERVED : ANIMATION_REQUEST_UNRESERVED); if (obj_dist(a1, a2) >= 9 || combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL)) { register_object_run_to_object(a1, a2, -1, 0); } } register_object_must_call(a1, a2, can_talk_to, -1); register_object_call(a1, a2, talk_to, -1); return register_end(); } // 0x413420 static int can_talk_to(Object* a1, Object* a2) { if (combat_is_shot_blocked(a1, a1->tile, a2->tile, a2, NULL) || obj_dist(a1, a2) >= 9) { if (a1 == obj_dude) { // You cannot get there. (used in actions.c) MessageListItem messageListItem; messageListItem.num = 2000; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } return -1; } return 0; } // 0x413488 static int talk_to(Object* a1, Object* a2) { scripts_request_dialog(a2); return 0; } // 0x413494 void action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor) { Attack* attack = (Attack*)mem_malloc(sizeof(*attack)); if (attack == NULL) { return; } Object* attacker; if (obj_new(&attacker, FID_0x20001F5, -1) == -1) { mem_free(attack); return; } obj_turn_off(attacker, NULL); attacker->flags |= OBJECT_TEMPORARY; obj_move_to_tile(attacker, tile, elevation, NULL); Object* defender = obj_blocking_at(NULL, tile, elevation); combat_ctd_init(attack, attacker, defender, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); attack->tile = tile; attack->attackerFlags = DAM_HIT; game_ui_disable(1); if (defender != NULL) { register_clear(defender); int damage; if (bypassArmor) { damage = maxDamage; } else { damage = compute_dmg_damage(minDamage, maxDamage, defender, &(attack->defenderKnockback), damageType); } attack->defenderDamage = damage; } death_checks(attack); if (animated) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_play_sfx(attacker, "whc1xxx1", 0); show_damage(attack, death_3[damageType], 0); register_object_must_call(attack, NULL, report_dmg, 0); register_object_must_erase(attacker); if (register_end() == -1) { obj_erase_object(attacker, NULL); mem_free(attack); return; } } else { if (defender != NULL) { if ((attack->defenderFlags & DAM_DEAD) != 0) { critter_kill(defender, -1, 1); } } // NOTE: Uninline. report_dmg(attack, NULL); obj_erase_object(attacker, NULL); } game_ui_enable(); } // 0x41363C static int report_dmg(Attack* attack, Object* a2) { combat_display(attack); apply_damage(attack, false); mem_free(attack); game_ui_enable(); return 0; } // Calculate damage by applying threshold and resistances. // // 0x413660 static int compute_dmg_damage(int min, int max, Object* obj, int* a4, int damageType) { if (!critter_flag_check(obj->pid, CRITTER_NO_KNOCKBACK)) { a4 = NULL; } int v8 = roll_random(min, max); int v10 = v8 - critterGetStat(obj, STAT_DAMAGE_THRESHOLD + damageType); if (v10 > 0) { v10 -= critterGetStat(obj, STAT_DAMAGE_RESISTANCE + damageType) * v10 / 100; } if (v10 < 0) { v10 = 0; } if (a4 != NULL) { if ((obj->flags & OBJECT_MULTIHEX) == 0 && damageType != DAMAGE_TYPE_ELECTRICAL) { *a4 = v10 / 10; } } return v10; } // 0x4136EC bool action_can_be_pushed(Object* a1, Object* a2) { // Cannot push anything but critters. if (FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) { return false; } // Cannot push itself. if (a1 == a2) { return false; } // Cannot push inactive critters. if (!critter_is_active(a2)) { return false; } if (action_can_talk_to(a1, a2) != 0) { return false; } // Can only push critters that have push handler. if (!scriptHasProc(a2->sid, SCRIPT_PROC_PUSH)) { return false; } if (isInCombat()) { if (a2->data.critter.combat.team == a1->data.critter.combat.team && a2 == a1->data.critter.combat.whoHitMe) { return false; } // TODO: Check. Object* whoHitMe = a2->data.critter.combat.whoHitMe; if (whoHitMe != NULL && whoHitMe->data.critter.combat.team == a1->data.critter.combat.team) { return false; } } return true; } // 0x413790 int action_push_critter(Object* a1, Object* a2) { if (!action_can_be_pushed(a1, a2)) { return -1; } int sid; if (obj_sid(a2, &sid) == 0) { scr_set_objs(sid, a1, a2); exec_script_proc(sid, SCRIPT_PROC_PUSH); bool scriptOverrides = false; Script* scr; if (scr_ptr(sid, &scr) != -1) { scriptOverrides = scr->scriptOverrides; } if (scriptOverrides) { return -1; } } int rotation = tile_dir(a1->tile, a2->tile); int tile; do { tile = tile_num_in_direction(a2->tile, rotation, 1); if (obj_blocking_at(a2, tile, a2->elevation) == NULL) { break; } tile = tile_num_in_direction(a2->tile, (rotation + 1) % ROTATION_COUNT, 1); if (obj_blocking_at(a2, tile, a2->elevation) == NULL) { break; } tile = tile_num_in_direction(a2->tile, (rotation + 5) % ROTATION_COUNT, 1); if (obj_blocking_at(a2, tile, a2->elevation) == NULL) { break; } tile = tile_num_in_direction(a2->tile, (rotation + 2) % ROTATION_COUNT, 1); if (obj_blocking_at(a2, tile, a2->elevation) == NULL) { break; } tile = tile_num_in_direction(a2->tile, (rotation + 4) % ROTATION_COUNT, 1); if (obj_blocking_at(a2, tile, a2->elevation) == NULL) { break; } tile = tile_num_in_direction(a2->tile, (rotation + 3) % ROTATION_COUNT, 1); if (obj_blocking_at(a2, tile, a2->elevation) == NULL) { break; } return -1; } while (0); int actionPoints; if (isInCombat()) { actionPoints = a2->data.critter.combat.ap; } else { actionPoints = -1; } register_begin(ANIMATION_REQUEST_RESERVED); register_object_turn_towards(a2, tile); register_object_move_to_tile(a2, tile, a2->elevation, actionPoints, 0); return register_end(); } // Returns -1 if can't see there (can't find a path there) // Returns -2 if it's too far (> 12 tiles). // // 0x413970 int action_can_talk_to(Object* a1, Object* a2) { if (make_path_func(a1, a1->tile, a2->tile, NULL, 0, obj_sight_blocking_at) == 0) { return -1; } if (tile_dist(a1->tile, a2->tile) > 12) { return -2; } return 0; } ================================================ FILE: src/game/actions.h ================================================ #ifndef FALLOUT_GAME_ACTIONS_H_ #define FALLOUT_GAME_ACTIONS_H_ #include #include "game/combat_defs.h" #include "game/object_types.h" #include "game/proto_types.h" extern unsigned int rotation; extern int obj_fid; extern int obj_pid_old; void switch_dude(); int action_knockback(Object* obj, int* anim, int maxDistance, int rotation, int delay); int action_blood(Object* obj, int anim, int delay); void show_damage_to_object(Object* a1, int damage, int flags, Object* weapon, bool isFallingBack, int knockbackDistance, int knockbackRotation, int a8, Object* a9, int a10); int show_damage_target(Attack* attack); int show_damage_extras(Attack* attack); void show_damage(Attack* attack, int a2, int a3); int action_attack(Attack* attack); int throw_change_fid(Object* object, int fid); int a_use_obj(Object* a1, Object* a2, Object* a3); int action_use_an_item_on_object(Object* a1, Object* a2, Object* a3); int action_use_an_object(Object* a1, Object* a2); int get_an_object(Object* item); int action_get_an_object(Object* critter, Object* item); int action_loot_container(Object* critter, Object* container); int action_skill_use(int a1); int action_use_skill_in_combat_error(Object* critter); int action_use_skill_on(Object* a1, Object* a2, int skill); Object* pick_object(int objectType, bool a2); int pick_hex(); bool is_hit_from_front(Object* a1, Object* a2); bool can_see(Object* a1, Object* a2); int pick_fall(Object* obj, int anim); bool action_explode_running(); int action_explode(int tile, int elevation, int minDamage, int maxDamage, Object* a5, bool a6); int action_talk_to(Object* a1, Object* a2); void action_dmg(int tile, int elevation, int minDamage, int maxDamage, int damageType, bool animated, bool bypassArmor); bool action_can_be_pushed(Object* a1, Object* a2); int action_push_critter(Object* a1, Object* a2); int action_can_talk_to(Object* a1, Object* a2); #endif /* FALLOUT_GAME_ACTIONS_H_ */ ================================================ FILE: src/game/amutex.c ================================================ #include "game/amutex.h" #define WIN32_LEAN_AND_MEAN #include // 0x530010 static HANDLE autorun_mutex; // 0x4139C0 bool autorun_mutex_create() { autorun_mutex = CreateMutexA(NULL, FALSE, "InterplayGenericAutorunMutex"); if (GetLastError() == ERROR_ALREADY_EXISTS) { CloseHandle(autorun_mutex); return false; } return true; } // 0x413A00 void autorun_mutex_destroy() { if (autorun_mutex != NULL) { CloseHandle(autorun_mutex); } } ================================================ FILE: src/game/amutex.h ================================================ #ifndef FALLOUT_GAME_AMUTEX_H_ #define FALLOUT_GAME_AMUTEX_H_ #include bool autorun_mutex_create(); void autorun_mutex_destroy(); #endif /* FALLOUT_GAME_AMUTEX_H_ */ ================================================ FILE: src/game/anim.c ================================================ #include "game/anim.h" #include #include #include "game/art.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "game/combat_defs.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/protinst.h" #include "game/roll.h" #include "game/scripts.h" #include "game/stat.h" #include "game/textobj.h" #include "game/tile.h" #include "game/trait.h" #include "plib/gnw/svga.h" #include "plib/gnw/vcr.h" #define ANIMATION_SEQUENCE_LIST_CAPACITY 32 #define ANIMATION_DESCRIPTION_LIST_CAPACITY 55 #define ANIMATION_SAD_LIST_CAPACITY 24 #define ANIMATION_SEQUENCE_FORCED 0x01 typedef enum AnimationKind { ANIM_KIND_MOVE_TO_OBJECT = 0, ANIM_KIND_MOVE_TO_TILE = 1, ANIM_KIND_MOVE_TO_TILE_STRAIGHT = 2, ANIM_KIND_MOVE_TO_TILE_STRAIGHT_AND_WAIT_FOR_COMPLETE = 3, ANIM_KIND_ANIMATE = 4, ANIM_KIND_ANIMATE_REVERSED = 5, ANIM_KIND_ANIMATE_AND_HIDE = 6, ANIM_KIND_ROTATE_TO_TILE = 7, ANIM_KIND_ROTATE_CLOCKWISE = 8, ANIM_KIND_ROTATE_COUNTER_CLOCKWISE = 9, ANIM_KIND_HIDE = 10, ANIM_KIND_CALLBACK = 11, ANIM_KIND_CALLBACK3 = 12, ANIM_KIND_SET_FLAG = 14, ANIM_KIND_UNSET_FLAG = 15, ANIM_KIND_TOGGLE_FLAT = 16, ANIM_KIND_SET_FID = 17, ANIM_KIND_TAKE_OUT_WEAPON = 18, ANIM_KIND_SET_LIGHT_DISTANCE = 19, ANIM_KIND_MOVE_ON_STAIRS = 20, ANIM_KIND_CHECK_FALLING = 23, ANIM_KIND_TOGGLE_OUTLINE = 24, ANIM_KIND_ANIMATE_FOREVER = 25, ANIM_KIND_26 = 26, ANIM_KIND_27 = 27, ANIM_KIND_NOOP = 28, } AnimationKind; typedef enum AnimationSequenceFlags { // Specifies that the animation sequence has high priority, it cannot be // cleared. ANIM_SEQ_PRIORITIZED = 0x01, // Specifies that the animation sequence started combat animation mode and // therefore should balance it with appropriate finish call. ANIM_SEQ_COMBAT_ANIM_STARTED = 0x02, // Specifies that the animation sequence is reserved (TODO: explain what it // actually means). ANIM_SEQ_RESERVED = 0x04, // Specifies that the animation sequence is in the process of adding actions // to it (that is in the middle of begin/end calls). ANIM_SEQ_ACCUMULATING = 0x08, // TODO: Add description. ANIM_SEQ_0x10 = 0x10, // TODO: Add description. ANIM_SEQ_0x20 = 0x20, // Specifies that the animation sequence is negligible and will be end // immediately when a new animation sequence is requested for the same // object. ANIM_SEQ_INSIGNIFICANT = 0x40, // Specifies that the animation sequence should not return to ANIM_STAND // when it's completed. ANIM_SEQ_NO_STAND = 0x80, } AnimationSequenceFlags; typedef enum AnimationSadFlags { // Specifies that the animation should play from end to start. ANIM_SAD_REVERSE = 0x01, // Specifies that the animation should use straight movement mode (as // opposed to normal movement mode). ANIM_SAD_STRAIGHT = 0x02, // Specifies that no frame change should occur during animation. ANIM_SAD_NO_ANIM = 0x04, // Specifies that the animation should be played fully from start to finish. // // NOTE: This flag is only used together with straight movement mode to // implement knockdown. Without this flag when the knockdown distance is // short, say 1 or 2 tiles, knockdown animation might not be completed by // the time critter reached it's destination. With this flag set animation // will be played to it's final frame. ANIM_SAD_WAIT_FOR_COMPLETION = 0x10, // Unknown, set once, never read. ANIM_SAD_0x20 = 0x20, // Specifies that the owner of the animation should be hidden when animation // is completed. ANIM_SAD_HIDE_ON_END = 0x40, // Specifies that the animation should never end. ANIM_SAD_FOREVER = 0x80, } AnimationSadFlags; typedef struct AnimationDescription { int kind; union { Object* owner; // - ANIM_KIND_CALLBACK // - ANIM_KIND_CALLBACK3 void* param2; }; union { // - ANIM_KIND_MOVE_TO_OBJECT Object* destination; // - ANIM_KIND_CALLBACK void* param1; }; union { // - ANIM_KIND_MOVE_TO_TILE // - ANIM_KIND_ANIMATE_AND_MOVE_TO_TILE_STRAIGHT // - ANIM_KIND_MOVE_TO_TILE_STRAIGHT struct { int tile; int elevation; }; // ANIM_KIND_SET_FID int fid; // ANIM_KIND_TAKE_OUT_WEAPON int weaponAnimationCode; // ANIM_KIND_SET_LIGHT_DISTANCE int lightDistance; // ANIM_KIND_TOGGLE_OUTLINE bool outline; }; int anim; int delay; // ANIM_KIND_CALLBACK AnimationCallback* callback; // ANIM_KIND_CALLBACK3 AnimationCallback3* callback3; union { // - ANIM_KIND_SET_FLAG // - ANIM_KIND_UNSET_FLAG unsigned int objectFlag; // - ANIM_KIND_HIDE // - ANIM_KIND_CALLBACK unsigned int extendedFlags; }; union { // - ANIM_KIND_MOVE_TO_TILE // - ANIM_KIND_MOVE_TO_OBJECT int actionPoints; // ANIM_KIND_26 int animationSequenceIndex; // ANIM_KIND_CALLBACK3 void* param3; }; CacheEntry* artCacheKey; } AnimationDescription; typedef struct AnimationSequence { int field_0; // Index of current animation in [animations] array or -1 if animations in // this sequence is not playing. int animationIndex; // Number of scheduled animations in [animations] array. int length; unsigned int flags; AnimationDescription animations[ANIMATION_DESCRIPTION_LIST_CAPACITY]; } AnimationSequence; typedef struct PathNode { int tile; int from; // actual type is likely char int rotation; int field_C; int field_10; } PathNode; // TODO: I don't know what `sad` means, but it's definitely better than // `STRUCT_530014`. Find a better name. typedef struct AnimationSad { unsigned int flags; Object* obj; int fid; // fid int anim; // Timestamp (in game ticks) when animation last occurred. unsigned int animationTimestamp; // Number of ticks per frame (taking art's fps and overall animation speed // settings into account). unsigned int ticksPerFrame; int animationSequenceIndex; int field_1C; // length of field_28 int field_20; // current index in field_28 int field_24; union { unsigned char rotations[3200]; StraightPathNode field_28[200]; }; } AnimationSad; static_assert(sizeof(AnimationSad) == 3240, "wrong size"); static int anim_free_slot(int a1); static int anim_preload(Object* object, int fid, CacheEntry** cacheEntryPtr); static void anim_cleanup(); static int anim_set_check(int a1); static int anim_set_continue(int a1, int a2); static int anim_set_end(int a1); static bool anim_can_use_door(Object* critter, Object* door); static int anim_move_to_object(Object* from, Object* to, int a3, int anim, int animationSequenceIndex); static int make_stair_path(Object* object, int from, int fromElevation, int to, int toElevation, StraightPathNode* a6, Object** obstaclePtr); static int anim_move_to_tile(Object* obj, int tile_num, int elev, int a4, int anim, int animationSequenceIndex); static int anim_move(Object* obj, int tile, int elev, int a3, int anim, int a5, int animationSequenceIndex); static int anim_move_straight_to_tile(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex, int flags); static void object_move(int index); static void object_straight_move(int index); static int anim_animate(Object* obj, int anim, int animationSequenceIndex, int flags); static void object_anim_compact(); static int anim_turn_towards(Object* obj, int delta, int animationSequenceIndex); static int check_gravity(int tile, int elevation); // 0x510718 static int curr_sad = 0; // 0x51071C static int curr_anim_set = -1; // 0x510720 static bool anim_in_init = false; // 0x510724 static bool anim_in_anim_stop = false; // 0x510728 static bool anim_in_bk = false; // 0x530014 static AnimationSad sad[ANIMATION_SAD_LIST_CAPACITY]; // 0x542FD4 static PathNode dad[2000]; // 0x54CC14 static AnimationSequence anim_set[ANIMATION_SEQUENCE_LIST_CAPACITY]; // 0x561814 static unsigned char seen[5000]; // 0x562B9C static PathNode child[2000]; // 0x56C7DC static int curr_anim_counter; // anim_init // 0x413A20 void anim_init() { anim_in_init = true; anim_reset(); anim_in_init = false; } // 0x413A40 void anim_reset() { if (!anim_in_init) { // NOTE: Uninline. anim_stop(); } curr_sad = 0; curr_anim_set = -1; for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { anim_set[index].field_0 = -1000; anim_set[index].flags = 0; } } // 0x413AB8 void anim_exit() { // NOTE: Uninline. anim_stop(); } // 0x413AF4 int register_begin(int requestOptions) { if (curr_anim_set != -1) { return -1; } if (anim_in_anim_stop) { return -1; } int v1 = anim_free_slot(requestOptions); if (v1 == -1) { return -1; } AnimationSequence* animationSequence = &(anim_set[v1]); animationSequence->flags |= ANIM_SEQ_ACCUMULATING; if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0) { animationSequence->flags |= ANIM_SEQ_RESERVED; } if ((requestOptions & ANIMATION_REQUEST_INSIGNIFICANT) != 0) { animationSequence->flags |= ANIM_SEQ_INSIGNIFICANT; } if ((requestOptions & ANIMATION_REQUEST_NO_STAND) != 0) { animationSequence->flags |= ANIM_SEQ_NO_STAND; } curr_anim_set = v1; curr_anim_counter = 0; return 0; } // 0x413B80 static int anim_free_slot(int requestOptions) { int v1 = -1; int v2 = 0; for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { AnimationSequence* animationSequence = &(anim_set[index]); if (animationSequence->field_0 != -1000 || (animationSequence->flags & ANIM_SEQ_ACCUMULATING) != 0 || (animationSequence->flags & ANIM_SEQ_0x20) != 0) { if (!(animationSequence->flags & ANIM_SEQ_RESERVED)) { v2++; } } else if (v1 == -1 && ((requestOptions & ANIMATION_REQUEST_0x100) == 0 || (animationSequence->flags & ANIM_SEQ_0x10) == 0)) { v1 = index; } } if (v1 == -1) { if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0) { debug_printf("Unable to begin reserved animation!\n"); } return -1; } else if ((requestOptions & ANIMATION_REQUEST_RESERVED) != 0 || v2 < 20) { return v1; } return -1; } // 0x413C20 int register_priority(int a1) { if (curr_anim_set == -1) { return -1; } if (a1 == 0) { return -1; } anim_set[curr_anim_set].flags |= ANIM_SEQ_PRIORITIZED; return 0; } // 0x413C4C int register_clear(Object* a1) { for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) { AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]); if (animationSequence->field_0 == -1000) { continue; } int animationDescriptionIndex; for (animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) { AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]); if (a1 != animationDescription->owner || animationDescription->kind == 11) { continue; } break; } if (animationDescriptionIndex == animationSequence->length) { continue; } if ((animationSequence->flags & ANIM_SEQ_PRIORITIZED) != 0) { return -2; } anim_set_end(animationSequenceIndex); return 0; } return -1; } // 0x413CCC int register_end() { if (curr_anim_set == -1) { return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); animationSequence->field_0 = 0; animationSequence->length = curr_anim_counter; animationSequence->animationIndex = -1; animationSequence->flags &= ~ANIM_SEQ_ACCUMULATING; animationSequence->animations[0].delay = 0; if (isInCombat()) { combat_anim_begin(); animationSequence->flags |= ANIM_SEQ_COMBAT_ANIM_STARTED; } int v1 = curr_anim_set; curr_anim_set = -1; if (!(animationSequence->flags & ANIM_SEQ_0x10)) { anim_set_continue(v1, 1); } return 0; } // NOTE: Inlined. // // 0x413D6C static int anim_preload(Object* object, int fid, CacheEntry** cacheEntryPtr) { *cacheEntryPtr = NULL; if (art_ptr_lock(fid, cacheEntryPtr) != NULL) { art_ptr_unlock(*cacheEntryPtr); *cacheEntryPtr = NULL; return 0; } return -1; } // 0x413D98 static void anim_cleanup() { if (curr_anim_set == -1) { return; } for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { anim_set[index].flags &= ~(ANIM_SEQ_ACCUMULATING | ANIM_SEQ_0x10); } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); for (int index = 0; index < curr_anim_counter; index++) { AnimationDescription* animationDescription = &(animationSequence->animations[index]); if (animationDescription->artCacheKey != NULL) { art_ptr_unlock(animationDescription->artCacheKey); } if (animationDescription->kind == ANIM_KIND_CALLBACK && animationDescription->callback == gsnd_anim_sound) { gsound_delete_sfx(animationDescription->param1); } } curr_anim_set = -1; } // 0x413E2C int check_registry(Object* obj) { if (curr_anim_set == -1) { return -1; } if (curr_anim_counter >= ANIMATION_DESCRIPTION_LIST_CAPACITY) { return -1; } if (obj == NULL) { return 0; } for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) { AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]); if (animationSequenceIndex != curr_anim_set && animationSequence->field_0 != -1000) { for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) { AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]); if (obj == animationDescription->owner && animationDescription->kind != 11) { if ((animationSequence->flags & ANIM_SEQ_INSIGNIFICANT) == 0) { return -1; } anim_set_end(animationSequenceIndex); } } } } return 0; } // Returns -1 if object is playing some animation. // // 0x413EC8 int anim_busy(Object* a1) { if (curr_anim_counter >= ANIMATION_DESCRIPTION_LIST_CAPACITY || a1 == NULL) { return 0; } for (int animationSequenceIndex = 0; animationSequenceIndex < ANIMATION_SEQUENCE_LIST_CAPACITY; animationSequenceIndex++) { AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]); if (animationSequenceIndex != curr_anim_set && animationSequence->field_0 != -1000) { for (int animationDescriptionIndex = 0; animationDescriptionIndex < animationSequence->length; animationDescriptionIndex++) { AnimationDescription* animationDescription = &(animationSequence->animations[animationDescriptionIndex]); if (a1 != animationDescription->owner) { continue; } if (animationDescription->kind == ANIM_KIND_CALLBACK) { continue; } if (animationSequence->length == 1 && animationDescription->anim == ANIM_STAND) { continue; } return -1; } } } return 0; } // 0x413F5C int register_object_move_to_object(Object* owner, Object* destination, int actionPoints, int delay) { if (check_registry(owner) == -1 || actionPoints == 0) { anim_cleanup(); return -1; } if (owner->tile == destination->tile && owner->elevation == destination->elevation) { return 0; } AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_TO_OBJECT; animationDescription->anim = ANIM_WALK; animationDescription->owner = owner; animationDescription->destination = destination; animationDescription->actionPoints = actionPoints; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return register_object_turn_towards(owner, destination->tile); } // 0x41405C int register_object_run_to_object(Object* owner, Object* destination, int actionPoints, int delay) { if (check_registry(owner) == -1 || actionPoints == 0) { anim_cleanup(); return -1; } if (owner->tile == destination->tile && owner->elevation == destination->elevation) { return 0; } if (critterIsOverloaded(owner)) { if (isPartyMember(owner)) { char formattedText[92]; MessageListItem messageListItem; if (owner == obj_dude) { // You are overloaded. strcpy(formattedText, getmsg(&misc_message_file, &messageListItem, 8000)); } else { // %s is overloaded. sprintf(formattedText, getmsg(&misc_message_file, &messageListItem, 8001), critter_name(owner)); } display_print(formattedText); } return register_object_move_to_object(owner, destination, actionPoints, delay); } AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_TO_OBJECT; animationDescription->owner = owner; animationDescription->destination = destination; if ((FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER && (owner->data.critter.combat.results & DAM_CRIP_LEG_ANY) != 0) || (owner == obj_dude && is_pc_flag(0) && !perk_level(obj_dude, PERK_SILENT_RUNNING)) || (!art_exists(art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, ANIM_RUNNING, 0, owner->rotation + 1)))) { animationDescription->anim = ANIM_WALK; } else { animationDescription->anim = ANIM_RUNNING; } animationDescription->actionPoints = actionPoints; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return register_object_turn_towards(owner, destination->tile); } // 0x414294 int register_object_move_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay) { if (check_registry(owner) == -1 || actionPoints == 0) { anim_cleanup(); return -1; } if (tile == owner->tile && elevation == owner->elevation) { return 0; } AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_TO_TILE; animationDescription->anim = ANIM_WALK; animationDescription->owner = owner; animationDescription->tile = tile; animationDescription->elevation = elevation; animationDescription->actionPoints = actionPoints; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x414394 int register_object_run_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay) { if (check_registry(owner) == -1 || actionPoints == 0) { anim_cleanup(); return -1; } if (tile == owner->tile && elevation == owner->elevation) { return 0; } if (critterIsOverloaded(owner)) { if (isPartyMember(owner)) { MessageListItem messageListItem; char formattedText[72]; if (owner == obj_dude) { // You are overloaded. strcpy(formattedText, getmsg(&misc_message_file, &messageListItem, 8000)); } else { // %s is overloaded. sprintf(formattedText, getmsg(&misc_message_file, &messageListItem, 8001), critter_name(owner)); } display_print(formattedText); } return register_object_move_to_tile(owner, tile, elevation, actionPoints, delay); } AnimationDescription* animationDescription = &(anim_set[curr_anim_set].animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_TO_TILE; animationDescription->owner = owner; animationDescription->tile = tile; animationDescription->elevation = elevation; if ((FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER && (owner->data.critter.combat.results & DAM_CRIP_LEG_ANY) != 0) || (owner == obj_dude && is_pc_flag(0) && !perk_level(obj_dude, PERK_SILENT_RUNNING)) || (!art_exists(art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, ANIM_RUNNING, 0, owner->rotation + 1)))) { animationDescription->anim = ANIM_WALK; } else { animationDescription->anim = ANIM_RUNNING; } animationDescription->actionPoints = actionPoints; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x4145D0 int register_object_move_straight_to_tile(Object* object, int tile, int elevation, int anim, int delay) { if (check_registry(object) == -1) { anim_cleanup(); return -1; } if (tile == object->tile && elevation == object->elevation) { return 0; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_TO_TILE_STRAIGHT; animationDescription->owner = object; animationDescription->tile = tile; animationDescription->elevation = elevation; animationDescription->anim = anim; animationDescription->delay = delay; int fid = art_id(FID_TYPE(object->fid), object->fid & 0xFFF, animationDescription->anim, (object->fid & 0xF000) >> 12, object->rotation + 1); // NOTE: Uninline. if (anim_preload(object, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x4146C4 int register_object_animate_and_move_straight(Object* owner, int tile, int elevation, int anim, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } if (tile == owner->tile && elevation == owner->elevation) { return 0; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_TO_TILE_STRAIGHT_AND_WAIT_FOR_COMPLETE; animationDescription->owner = owner; animationDescription->tile = tile; animationDescription->elevation = elevation; animationDescription->anim = anim; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // NOTE: Unused. // // 0x4147B int register_object_move_on_stairs(Object* owner, Object* stairs, int delay) { int anim; int destTile; int destElevation; AnimationSequence* animationSequence; AnimationDescription* animationDescription; int fid; if (check_registry(owner) == -1) { anim_cleanup(); return -1; } if (owner->elevation == stairs->elevation) { anim = ANIM_UP_STAIRS_LEFT; destTile = stairs->tile + 4; destElevation = stairs->elevation + 1; } else { anim = ANIM_DOWN_STAIRS_RIGHT; destTile = stairs->tile + 200; destElevation = stairs->elevation; } if (destTile == owner->tile && destElevation == owner->elevation) { return 0; } animationSequence = &(anim_set[curr_anim_set]); animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_MOVE_ON_STAIRS; animationDescription->owner = owner; animationDescription->tile = destTile; animationDescription->elevation = destElevation; animationDescription->anim = anim; animationDescription->delay = delay; fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // NOTE: Unused. // // 0x4148F0 int register_object_check_falling(Object* owner, int delay) { AnimationSequence* animationSequence; AnimationDescription* animationDescription; int fid; if (check_registry(owner) == -1) { anim_cleanup(); return -1; } animationSequence = &(anim_set[curr_anim_set]); animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_CHECK_FALLING; animationDescription->anim = ANIM_FALLING; animationDescription->owner = owner; animationDescription->delay = delay; fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x4149D0 int register_object_animate(Object* owner, int anim, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ANIMATE; animationDescription->owner = owner; animationDescription->anim = anim; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x414AA8 int register_object_animate_reverse(Object* owner, int anim, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ANIMATE_REVERSED; animationDescription->owner = owner; animationDescription->anim = anim; animationDescription->delay = delay; animationDescription->artCacheKey = NULL; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, animationDescription->anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x414B7C int register_object_animate_and_hide(Object* owner, int anim, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ANIMATE_AND_HIDE; animationDescription->owner = owner; animationDescription->anim = anim; animationDescription->delay = delay; animationDescription->artCacheKey = NULL; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x414C50 int register_object_turn_towards(Object* owner, int tile) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ROTATE_TO_TILE; animationDescription->delay = -1; animationDescription->artCacheKey = NULL; animationDescription->owner = owner; animationDescription->tile = tile; curr_anim_counter++; return 0; } // 0x414CC8 int register_object_inc_rotation(Object* owner) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ROTATE_CLOCKWISE; animationDescription->delay = -1; animationDescription->artCacheKey = NULL; animationDescription->owner = owner; curr_anim_counter++; return 0; } // 0x414D38 int register_object_dec_rotation(Object* owner) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ROTATE_COUNTER_CLOCKWISE; animationDescription->delay = -1; animationDescription->artCacheKey = NULL; animationDescription->owner = owner; curr_anim_counter++; return 0; } // NOTE: Unused. // // 0x414DA8 int register_object_erase(Object* object) { if (check_registry(object) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_HIDE; animationDescription->delay = -1; animationDescription->artCacheKey = NULL; animationDescription->extendedFlags = 0; animationDescription->owner = object; curr_anim_counter++; return 0; } // 0x414E20 int register_object_must_erase(Object* object) { if (check_registry(object) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_HIDE; animationDescription->delay = -1; animationDescription->artCacheKey = NULL; animationDescription->extendedFlags = ANIMATION_SEQUENCE_FORCED; animationDescription->owner = object; curr_anim_counter++; return 0; } // 0x414E98 int register_object_call(void* a1, void* a2, AnimationCallback* proc, int delay) { if (check_registry(NULL) == -1 || proc == NULL) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_CALLBACK; animationDescription->extendedFlags = 0; animationDescription->artCacheKey = NULL; animationDescription->param2 = a2; animationDescription->param1 = a1; animationDescription->callback = proc; animationDescription->delay = delay; curr_anim_counter++; return 0; } // Same as `register_object_call` but accepting 3 parameters. // // 0x414F20 int register_object_call3(void* a1, void* a2, void* a3, AnimationCallback3* proc, int delay) { if (check_registry(NULL) == -1 || proc == NULL) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_CALLBACK3; animationDescription->extendedFlags = 0; animationDescription->artCacheKey = NULL; animationDescription->param2 = a2; animationDescription->param1 = a1; animationDescription->callback3 = proc; animationDescription->param3 = a3; animationDescription->delay = delay; curr_anim_counter++; return 0; } // 0x414FAC int register_object_must_call(void* a1, void* a2, AnimationCallback* proc, int delay) { if (check_registry(NULL) == -1 || proc == NULL) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_CALLBACK; animationDescription->extendedFlags = ANIMATION_SEQUENCE_FORCED; animationDescription->artCacheKey = NULL; animationDescription->param2 = a2; animationDescription->param1 = a1; animationDescription->callback = proc; animationDescription->delay = delay; curr_anim_counter++; return 0; } // NOTE: Unused. // // The [flag] parameter should be one of OBJECT_* flags. The way it's handled // down the road implies it should not be a group of flags (joined with bitwise // OR), but a one particular flag. // // 0x415034 int register_object_fset(Object* object, int flag, int delay) { if (check_registry(object) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_SET_FLAG; animationDescription->artCacheKey = NULL; animationDescription->owner = object; animationDescription->objectFlag = flag; animationDescription->delay = delay; curr_anim_counter++; return 0; } // The [flag] parameter should be one of OBJECT_* flags. The way it's handled // down the road implies it should not be a group of flags (joined with bitwise // OR), but a one particular flag. // // 0x4150A8 int register_object_funset(Object* object, int flag, int delay) { if (check_registry(object) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_UNSET_FLAG; animationDescription->artCacheKey = NULL; animationDescription->owner = object; animationDescription->objectFlag = flag; animationDescription->delay = delay; curr_anim_counter++; return 0; } // 0x41518C int register_object_change_fid(Object* owner, int fid, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_SET_FID; animationDescription->owner = owner; animationDescription->fid = fid; animationDescription->delay = delay; // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x415238 int register_object_take_out(Object* owner, int weaponAnimationCode, int delay) { const char* sfx = gsnd_build_character_sfx_name(owner, ANIM_TAKE_OUT, weaponAnimationCode); if (register_object_play_sfx(owner, sfx, delay) == -1) { return -1; } if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_TAKE_OUT_WEAPON; animationDescription->anim = ANIM_TAKE_OUT; animationDescription->delay = 0; animationDescription->owner = owner; animationDescription->weaponAnimationCode = weaponAnimationCode; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, ANIM_TAKE_OUT, weaponAnimationCode, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x415334 int register_object_light(Object* owner, int lightDistance, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_SET_LIGHT_DISTANCE; animationDescription->artCacheKey = NULL; animationDescription->owner = owner; animationDescription->lightDistance = lightDistance; animationDescription->delay = delay; curr_anim_counter++; return 0; } // NOTE: Unused. // // 0x4153A8 int register_object_outline(Object* object, bool outline, int delay) { if (check_registry(object) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_TOGGLE_OUTLINE; animationDescription->artCacheKey = NULL; animationDescription->owner = object; animationDescription->outline = outline; animationDescription->delay = delay; curr_anim_counter++; return 0; } // 0x41541C int register_object_play_sfx(Object* owner, const char* soundEffectName, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_CALLBACK; animationDescription->owner = owner; if (soundEffectName != NULL) { int volume = gsound_compute_relative_volume(owner); animationDescription->param1 = gsound_load_sound_volume(soundEffectName, owner, volume); if (animationDescription->param1 != NULL) { animationDescription->callback = gsnd_anim_sound; } else { animationDescription->kind = ANIM_KIND_NOOP; } } else { animationDescription->kind = ANIM_KIND_NOOP; } animationDescription->artCacheKey = NULL; animationDescription->delay = delay; curr_anim_counter++; return 0; } // 0x4154C4 int register_object_animate_forever(Object* owner, int anim, int delay) { if (check_registry(owner) == -1) { anim_cleanup(); return -1; } AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->kind = ANIM_KIND_ANIMATE_FOREVER; animationDescription->owner = owner; animationDescription->anim = anim; animationDescription->delay = delay; int fid = art_id(FID_TYPE(owner->fid), owner->fid & 0xFFF, anim, (owner->fid & 0xF000) >> 12, owner->rotation + 1); // NOTE: Uninline. if (anim_preload(owner, fid, &(animationDescription->artCacheKey)) == -1) { anim_cleanup(); return -1; } curr_anim_counter++; return 0; } // 0x415598 int register_ping(int a1, int delay) { if (check_registry(NULL) == -1) { anim_cleanup(); return -1; } int animationSequenceIndex = anim_free_slot(a1 | ANIMATION_REQUEST_0x100); if (animationSequenceIndex == -1) { return -1; } anim_set[animationSequenceIndex].flags = ANIM_SEQ_0x10; AnimationSequence* animationSequence = &(anim_set[curr_anim_set]); AnimationDescription* animationDescription = &(animationSequence->animations[curr_anim_counter]); animationDescription->owner = NULL; animationDescription->kind = ANIM_KIND_26; animationDescription->artCacheKey = NULL; animationDescription->animationSequenceIndex = animationSequenceIndex; animationDescription->delay = delay; curr_anim_counter++; return 0; } // 0x4156A8 static int anim_set_check(int animationSequenceIndex) { if (animationSequenceIndex == -1) { return -1; } AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]); if (animationSequence->field_0 == -1000) { return -1; } while (1) { if (animationSequence->field_0 >= animationSequence->length) { return 0; } if (animationSequence->field_0 > animationSequence->animationIndex) { AnimationDescription* animationDescription = &(animationSequence->animations[animationSequence->field_0]); if (animationDescription->delay < 0) { return 0; } if (animationDescription->delay > 0) { animationDescription->delay--; return 0; } } AnimationDescription* animationDescription = &(animationSequence->animations[animationSequence->field_0++]); int rc; Rect rect; switch (animationDescription->kind) { case ANIM_KIND_MOVE_TO_OBJECT: rc = anim_move_to_object(animationDescription->owner, animationDescription->destination, animationDescription->actionPoints, animationDescription->anim, animationSequenceIndex); break; case ANIM_KIND_MOVE_TO_TILE: rc = anim_move_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->actionPoints, animationDescription->anim, animationSequenceIndex); break; case ANIM_KIND_MOVE_TO_TILE_STRAIGHT: rc = anim_move_straight_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex, 0); break; case ANIM_KIND_MOVE_TO_TILE_STRAIGHT_AND_WAIT_FOR_COMPLETE: rc = anim_move_straight_to_tile(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex, ANIM_SAD_WAIT_FOR_COMPLETION); break; case ANIM_KIND_ANIMATE: rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, 0); break; case ANIM_KIND_ANIMATE_REVERSED: rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_REVERSE); break; case ANIM_KIND_ANIMATE_AND_HIDE: rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_HIDE_ON_END); if (rc == -1) { // NOTE: Uninline. rc = anim_hide(animationDescription->owner, animationSequenceIndex); } break; case ANIM_KIND_ANIMATE_FOREVER: rc = anim_animate(animationDescription->owner, animationDescription->anim, animationSequenceIndex, ANIM_SAD_FOREVER); break; case ANIM_KIND_ROTATE_TO_TILE: if (!critter_is_prone(animationDescription->owner)) { int rotation = tile_dir(animationDescription->owner->tile, animationDescription->tile); dude_stand(animationDescription->owner, rotation, -1); } anim_set_continue(animationSequenceIndex, 0); rc = 0; break; case ANIM_KIND_ROTATE_CLOCKWISE: rc = anim_turn_towards(animationDescription->owner, 1, animationSequenceIndex); break; case ANIM_KIND_ROTATE_COUNTER_CLOCKWISE: rc = anim_turn_towards(animationDescription->owner, -1, animationSequenceIndex); break; case ANIM_KIND_HIDE: // NOTE: Uninline. rc = anim_hide(animationDescription->owner, animationSequenceIndex); break; case ANIM_KIND_CALLBACK: rc = animationDescription->callback(animationDescription->param1, animationDescription->param2); if (rc == 0) { rc = anim_set_continue(animationSequenceIndex, 0); } break; case ANIM_KIND_CALLBACK3: rc = animationDescription->callback3(animationDescription->param1, animationDescription->param2, animationDescription->param3); if (rc == 0) { rc = anim_set_continue(animationSequenceIndex, 0); } break; case ANIM_KIND_SET_FLAG: if (animationDescription->objectFlag == OBJECT_LIGHTING) { if (obj_turn_on_light(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } } else if (animationDescription->objectFlag == OBJECT_HIDDEN) { if (obj_turn_off(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } } else { animationDescription->owner->flags |= animationDescription->objectFlag; } rc = anim_set_continue(animationSequenceIndex, 0); break; case ANIM_KIND_UNSET_FLAG: if (animationDescription->objectFlag == OBJECT_LIGHTING) { if (obj_turn_off_light(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } } else if (animationDescription->objectFlag == OBJECT_HIDDEN) { if (obj_turn_on(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } } else { animationDescription->owner->flags &= ~animationDescription->objectFlag; } rc = anim_set_continue(animationSequenceIndex, 0); break; case ANIM_KIND_TOGGLE_FLAT: if (obj_toggle_flat(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } rc = anim_set_continue(animationSequenceIndex, 0); break; case ANIM_KIND_SET_FID: rc = anim_change_fid(animationDescription->owner, animationSequenceIndex, animationDescription->fid); break; case ANIM_KIND_TAKE_OUT_WEAPON: rc = anim_animate(animationDescription->owner, ANIM_TAKE_OUT, animationSequenceIndex, animationDescription->tile); break; case ANIM_KIND_SET_LIGHT_DISTANCE: obj_set_light(animationDescription->owner, animationDescription->lightDistance, animationDescription->owner->lightIntensity, &rect); tile_refresh_rect(&rect, animationDescription->owner->elevation); rc = anim_set_continue(animationSequenceIndex, 0); break; case ANIM_KIND_MOVE_ON_STAIRS: rc = anim_move_on_stairs(animationDescription->owner, animationDescription->tile, animationDescription->elevation, animationDescription->anim, animationSequenceIndex); break; case ANIM_KIND_CHECK_FALLING: rc = check_for_falling(animationDescription->owner, animationDescription->anim, animationSequenceIndex); break; case ANIM_KIND_TOGGLE_OUTLINE: if (animationDescription->outline) { if (obj_turn_on_outline(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } } else { if (obj_turn_off_outline(animationDescription->owner, &rect) == 0) { tile_refresh_rect(&rect, animationDescription->owner->elevation); } } rc = anim_set_continue(animationSequenceIndex, 0); break; case ANIM_KIND_26: anim_set[animationDescription->animationSequenceIndex].flags &= ~ANIM_SEQ_0x10; rc = anim_set_continue(animationDescription->animationSequenceIndex, 1); if (rc != -1) { rc = anim_set_continue(animationSequenceIndex, 0); } break; case ANIM_KIND_NOOP: rc = anim_set_continue(animationSequenceIndex, 0); break; default: rc = -1; break; } if (rc == -1) { anim_set_end(animationSequenceIndex); } if (animationSequence->field_0 == -1000) { return -1; } } } // 0x415B44 static int anim_set_continue(int animationSequenceIndex, int a2) { if (animationSequenceIndex == -1) { return -1; } AnimationSequence* animationSequence = &(anim_set[animationSequenceIndex]); if (animationSequence->field_0 == -1000) { return -1; } animationSequence->animationIndex++; if (animationSequence->animationIndex == animationSequence->length) { return anim_set_end(animationSequenceIndex); } else { if (a2) { return anim_set_check(animationSequenceIndex); } } return 0; } // 0x415B9C static int anim_set_end(int animationSequenceIndex) { AnimationSequence* animationSequence; AnimationDescription* animationDescription; int i; Rect v27; if (animationSequenceIndex == -1) { return -1; } animationSequence = &(anim_set[animationSequenceIndex]); if (animationSequence->field_0 == -1000) { return -1; } for (i = 0; i < curr_sad; i++) { AnimationSad* sad_entry = &(sad[i]); if (sad_entry->animationSequenceIndex == animationSequenceIndex) { sad_entry->field_20 = -1000; } } for (i = 0; i < animationSequence->length; i++) { animationDescription = &(animationSequence->animations[i]); if (animationDescription->kind == ANIM_KIND_HIDE && ((i < animationSequence->animationIndex) || (animationDescription->extendedFlags & ANIMATION_SEQUENCE_FORCED))) { obj_erase_object(animationDescription->owner, &v27); tile_refresh_rect(&v27, animationDescription->owner->elevation); } } for (i = 0; i < animationSequence->length; i++) { animationDescription = &(animationSequence->animations[i]); if (animationDescription->artCacheKey) { art_ptr_unlock(animationDescription->artCacheKey); } if (animationDescription->kind != 11 && animationDescription->kind != 12) { // TODO: Check. if (animationDescription->kind != ANIM_KIND_26) { Object* owner = animationDescription->owner; if (FID_TYPE(owner->fid) == OBJ_TYPE_CRITTER) { int j = 0; for (; j < i; j++) { AnimationDescription* ad = &(animationSequence->animations[j]); if (owner == ad->owner) { if (ad->kind != ANIM_KIND_CALLBACK && ad->kind != ANIM_KIND_CALLBACK3) { break; } } } if (i == j) { int k = 0; for (; k < animationSequence->animationIndex; k++) { AnimationDescription* ad = &(animationSequence->animations[k]); if (ad->kind == ANIM_KIND_HIDE && ad->owner == owner) { break; } } if (k == animationSequence->animationIndex) { for (int m = 0; m < curr_sad; m++) { if (sad[m].obj == owner) { sad[m].field_20 = -1000; break; } } if ((animationSequence->flags & ANIM_SEQ_NO_STAND) == 0 && !critter_is_prone(owner)) { dude_stand(owner, owner->rotation, -1); } } } } } } else if (i >= animationSequence->field_0) { if (animationDescription->extendedFlags & ANIMATION_SEQUENCE_FORCED) { animationDescription->callback(animationDescription->param1, animationDescription->param2); } else { if (animationDescription->kind == ANIM_KIND_CALLBACK && animationDescription->callback == gsnd_anim_sound) { gsound_delete_sfx(animationDescription->param1); } } } } animationSequence->animationIndex = -1; animationSequence->field_0 = -1000; if ((animationSequence->flags & ANIM_SEQ_COMBAT_ANIM_STARTED) != 0) { combat_anim_finished(); } if (anim_in_bk) { animationSequence->flags = ANIM_SEQ_0x20; } else { animationSequence->flags = 0; } return 0; } // 0x415E24 static bool anim_can_use_door(Object* critter, Object* door) { if (critter == obj_dude) { if (!obj_portal_is_walk_thru(door)) { return false; } } if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) { return false; } if (FID_TYPE(door->fid) != OBJ_TYPE_SCENERY) { return false; } int bodyType = critter_body_type(critter); if (bodyType != BODY_TYPE_BIPED && bodyType != BODY_TYPE_ROBOTIC) { return false; } Proto* proto; if (proto_ptr(door->pid, &proto) == -1) { return false; } if (proto->scenery.type != SCENERY_TYPE_DOOR) { return false; } if (obj_is_locked(door)) { return false; } if (critterGetKillType(critter) == KILL_TYPE_GECKO) { return false; } return true; } // 0x415EE8 int make_path(Object* object, int from, int to, unsigned char* rotations, int a5) { return make_path_func(object, from, to, rotations, a5, obj_blocking_at); } // 0x415EFC int make_path_func(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback) { if (a5) { if (callback(object, to, object->elevation) != NULL) { return 0; } } bool isCritter = false; int kt = 0; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { isCritter = true; kt = critterGetKillType(object); } bool isNotInCombat = !isInCombat(); memset(seen, 0, sizeof(seen)); seen[from / 8] |= 1 << (from & 7); child[0].tile = from; child[0].from = -1; child[0].rotation = 0; child[0].field_C = EST(from, to); child[0].field_10 = 0; for (int index = 1; index < 2000; index += 1) { child[index].tile = -1; } int toScreenX; int toScreenY; tile_coord(to, &toScreenX, &toScreenY, object->elevation); int closedPathNodeListLength = 0; int openPathNodeListLength = 1; PathNode temp; while (1) { int v63 = -1; PathNode* prev = NULL; int v12 = 0; for (int index = 0; v12 < openPathNodeListLength; index += 1) { PathNode* curr = &(child[index]); if (curr->tile != -1) { v12++; if (v63 == -1 || (curr->field_C + curr->field_10) < (prev->field_C + prev->field_10)) { prev = curr; v63 = index; } } } PathNode* curr = &(child[v63]); memcpy(&temp, curr, sizeof(temp)); openPathNodeListLength -= 1; curr->tile = -1; if (temp.tile == to) { if (openPathNodeListLength == 0) { openPathNodeListLength = 1; } break; } PathNode* curr1 = &(dad[closedPathNodeListLength]); memcpy(curr1, &temp, sizeof(temp)); closedPathNodeListLength += 1; if (closedPathNodeListLength == 2000) { return 0; } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int tile = tile_num_in_direction(temp.tile, rotation, 1); int bit = 1 << (tile & 7); if ((seen[tile / 8] & bit) != 0) { continue; } if (tile != to) { Object* v24 = callback(object, tile, object->elevation); if (v24 != NULL) { if (!anim_can_use_door(object, v24)) { continue; } } } int v25 = 0; for (; v25 < 2000; v25++) { if (child[v25].tile == -1) { break; } } openPathNodeListLength += 1; if (openPathNodeListLength == 2000) { return 0; } seen[tile / 8] |= bit; PathNode* v27 = &(child[v25]); v27->tile = tile; v27->from = temp.tile; v27->rotation = rotation; int newX; int newY; tile_coord(tile, &newX, &newY, object->elevation); v27->field_C = idist(newX, newY, toScreenX, toScreenY); v27->field_10 = temp.field_10 + 50; if (isNotInCombat && temp.rotation != rotation) { v27->field_10 += 10; } if (isCritter) { Object* o = obj_find_first_at_tile(object->elevation, v27->tile); while (o != NULL) { if (o->pid >= 0x20003D9 && o->pid <= 0x20003DC) { break; } o = obj_find_next_at_tile(); } if (o != NULL) { if (kt == KILL_TYPE_GECKO) { v27->field_10 += 100; } else { v27->field_10 += 400; } } } } if (openPathNodeListLength == 0) { break; } } if (openPathNodeListLength != 0) { unsigned char* v39 = rotations; int index = 0; for (; index < 800; index++) { if (temp.tile == from) { break; } if (v39 != NULL) { *v39 = temp.rotation & 0xFF; v39 += 1; } int j = 0; while (dad[j].tile != temp.from) { j++; } PathNode* v36 = &(dad[j]); memcpy(&temp, v36, sizeof(temp)); } if (rotations != NULL) { // Looks like array resevering, probably because A* finishes it's path from end to start, // this probably reverses it start-to-end. unsigned char* beginning = rotations; unsigned char* ending = rotations + index - 1; int middle = index / 2; for (int index = 0; index < middle; index++) { unsigned char rotation = *ending; *ending = *beginning; *beginning = rotation; ending -= 1; beginning += 1; } } return index; } return 0; } // 0x41633C int idist(int x1, int y1, int x2, int y2) { int dx = x2 - x1; if (dx < 0) { dx = -dx; } int dy = y2 - y1; if (dy < 0) { dy = -dy; } int dm = (dx <= dy) ? dx : dy; return dx + dy - (dm / 2); } // 0x416360 int EST(int tile1, int tile2) { int x1; int y1; tile_coord(tile1, &x1, &y1, map_elevation); int x2; int y2; tile_coord(tile2, &x2, &y2, map_elevation); return idist(x1, y1, x2, y2); } // 0x4163AC int make_straight_path(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6) { return make_straight_path_func(a1, from, to, pathNodes, a5, a6, obj_blocking_at); } // TODO: Rather complex, but understandable, needs testing. // // 0x4163C8 int make_straight_path_func(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6, PathBuilderCallback* callback) { if (a5 != NULL) { Object* v11 = callback(a1, from, a1->elevation); if (v11 != NULL) { if (v11 != *a5 && (a6 != 32 || (v11->flags & OBJECT_SHOOT_THRU) == 0)) { *a5 = v11; return 0; } } } int fromX; int fromY; tile_coord(from, &fromX, &fromY, a1->elevation); fromX += 16; fromY += 8; int toX; int toY; tile_coord(to, &toX, &toY, a1->elevation); toX += 16; toY += 8; int stepX; int deltaX = toX - fromX; if (deltaX > 0) stepX = 1; else if (deltaX < 0) stepX = -1; else stepX = 0; int stepY; int deltaY = toY - fromY; if (deltaY > 0) stepY = 1; else if (deltaY < 0) stepY = -1; else stepY = 0; int v48 = 2 * abs(toX - fromX); int v47 = 2 * abs(toY - fromY); int tileX = fromX; int tileY = fromY; int pathNodeIndex = 0; int prevTile = from; int v22 = 0; int tile; if (v48 <= v47) { int middle = v48 - v47 / 2; while (true) { tile = tile_num(tileX, tileY, a1->elevation); v22 += 1; if (v22 == a6) { if (pathNodeIndex >= 200) { return 0; } if (pathNodes != NULL) { StraightPathNode* pathNode = &(pathNodes[pathNodeIndex]); pathNode->tile = tile; pathNode->elevation = a1->elevation; tile_coord(tile, &fromX, &fromY, a1->elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } v22 = 0; pathNodeIndex++; } if (tileY == toY) { if (a5 != NULL) { *a5 = NULL; } break; } if (middle >= 0) { tileX += stepX; middle -= v47; } tileY += stepY; middle += v48; if (tile != prevTile) { if (a5 != NULL) { Object* obj = callback(a1, tile, a1->elevation); if (obj != NULL) { if (obj != *a5 && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) { *a5 = obj; break; } } } prevTile = tile; } } } else { int middle = v47 - v48 / 2; while (true) { tile = tile_num(tileX, tileY, a1->elevation); v22 += 1; if (v22 == a6) { if (pathNodeIndex >= 200) { return 0; } if (pathNodes != NULL) { StraightPathNode* pathNode = &(pathNodes[pathNodeIndex]); pathNode->tile = tile; pathNode->elevation = a1->elevation; tile_coord(tile, &fromX, &fromY, a1->elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } v22 = 0; pathNodeIndex++; } if (tileX == toX) { if (a5 != NULL) { *a5 = NULL; } break; } if (middle >= 0) { tileY += stepY; middle -= v48; } tileX += stepX; middle += v47; if (tile != prevTile) { if (a5 != NULL) { Object* obj = callback(a1, tile, a1->elevation); if (obj != NULL) { if (obj != *a5 && (a6 != 32 || (obj->flags & OBJECT_SHOOT_THRU) == 0)) { *a5 = obj; break; } } } prevTile = tile; } } } if (v22 != 0) { if (pathNodeIndex >= 200) { return 0; } if (pathNodes != NULL) { StraightPathNode* pathNode = &(pathNodes[pathNodeIndex]); pathNode->tile = tile; pathNode->elevation = a1->elevation; tile_coord(tile, &fromX, &fromY, a1->elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } pathNodeIndex += 1; } else { if (pathNodeIndex > 0 && pathNodes != NULL) { pathNodes[pathNodeIndex - 1].elevation = a1->elevation; } } return pathNodeIndex; } // 0x4167F8 static int anim_move_to_object(Object* from, Object* to, int a3, int anim, int animationSequenceIndex) { bool hidden = (to->flags & OBJECT_HIDDEN); to->flags |= OBJECT_HIDDEN; int moveSadIndex = anim_move(from, to->tile, to->elevation, -1, anim, 0, animationSequenceIndex); if (!hidden) { to->flags &= ~OBJECT_HIDDEN; } if (moveSadIndex == -1) { return -1; } AnimationSad* sad_entry = &(sad[moveSadIndex]); // NOTE: Original code is somewhat different. Due to some kind of // optimization this value is either 1 or 2, which is later used in // subsequent calculations and rotations array lookup. bool isMultihex = (from->flags & OBJECT_MULTIHEX); sad_entry->field_1C -= (isMultihex ? 2 : 1); if (sad_entry->field_1C <= 0) { sad_entry->field_20 = -1000; anim_set_continue(animationSequenceIndex, 0); } sad_entry->field_24 = tile_num_in_direction(to->tile, sad_entry->rotations[isMultihex ? sad_entry->field_1C + 1 : sad_entry->field_1C], 1); if (isMultihex) { sad_entry->field_24 = tile_num_in_direction(sad_entry->field_24, sad_entry->rotations[sad_entry->field_1C], 1); } if (a3 != -1 && a3 < sad_entry->field_1C) { sad_entry->field_1C = a3; } return 0; } // 0x41695C static int make_stair_path(Object* object, int from, int fromElevation, int to, int toElevation, StraightPathNode* a6, Object** obstaclePtr) { int elevation = fromElevation; if (elevation > toElevation) { elevation = toElevation; } int fromX; int fromY; tile_coord(from, &fromX, &fromY, fromElevation); fromX += 16; fromY += 8; int toX; int toY; tile_coord(to, &toX, &toY, toElevation); toX += 16; toY += 8; if (obstaclePtr != NULL) { *obstaclePtr = NULL; } int ddx = 2 * abs(toX - fromX); int stepX; int deltaX = toX - fromX; if (deltaX > 0) { stepX = 1; } else if (deltaX < 0) { stepX = -1; } else { stepX = 0; } int ddy = 2 * abs(toY - fromY); int stepY; int deltaY = toY - fromY; if (deltaY > 0) { stepY = 1; } else if (deltaY < 0) { stepY = -1; } else { stepY = 0; } int tileX = fromX; int tileY = fromY; int pathNodeIndex = 0; int prevTile = from; int iteration = 0; int tile; if (ddx > ddy) { int middle = ddy - ddx / 2; while (true) { tile = tile_num(tileX, tileY, elevation); iteration += 1; if (iteration == 16) { if (pathNodeIndex >= 200) { return 0; } if (a6 != NULL) { StraightPathNode* pathNode = &(a6[pathNodeIndex]); pathNode->tile = tile; pathNode->elevation = elevation; tile_coord(tile, &fromX, &fromY, elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } iteration = 0; pathNodeIndex++; } if (tileX == toX) { break; } if (middle >= 0) { tileY += stepY; middle -= ddx; } tileX += stepX; middle += ddy; if (tile != prevTile) { if (obstaclePtr != NULL) { *obstaclePtr = obj_blocking_at(object, tile, object->elevation); if (*obstaclePtr != NULL) { break; } } prevTile = tile; } } } else { int middle = ddx - ddy / 2; while (true) { tile = tile_num(tileX, tileY, elevation); iteration += 1; if (iteration == 16) { if (pathNodeIndex >= 200) { return 0; } if (a6 != NULL) { StraightPathNode* pathNode = &(a6[pathNodeIndex]); pathNode->tile = tile; pathNode->elevation = elevation; tile_coord(tile, &fromX, &fromY, elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } iteration = 0; pathNodeIndex++; } if (tileY == toY) { break; } if (middle >= 0) { tileX += stepX; middle -= ddy; } tileY += stepY; middle += ddx; if (tile != prevTile) { if (obstaclePtr != NULL) { *obstaclePtr = obj_blocking_at(object, tile, object->elevation); if (*obstaclePtr != NULL) { break; } } prevTile = tile; } } } if (iteration != 0) { if (pathNodeIndex >= 200) { return 0; } if (a6 != NULL) { StraightPathNode* pathNode = &(a6[pathNodeIndex]); pathNode->tile = tile; pathNode->elevation = elevation; tile_coord(tile, &fromX, &fromY, elevation); pathNode->x = tileX - fromX - 16; pathNode->y = tileY - fromY - 8; } pathNodeIndex++; } else { if (pathNodeIndex > 0) { if (a6 != NULL) { a6[pathNodeIndex - 1].elevation = toElevation; } } } return pathNodeIndex; } // 0x416CFC static int anim_move_to_tile(Object* obj, int tile, int elev, int a4, int anim, int animationSequenceIndex) { int v1; v1 = anim_move(obj, tile, elev, -1, anim, 0, animationSequenceIndex); if (v1 == -1) { return -1; } if (obj_blocking_at(obj, tile, elev)) { AnimationSad* sad_entry = &(sad[v1]); sad_entry->field_1C--; if (sad_entry->field_1C <= 0) { sad_entry->field_20 = -1000; anim_set_continue(animationSequenceIndex, 0); } sad_entry->field_24 = tile_num_in_direction(tile, sad_entry->rotations[sad_entry->field_1C], 1); if (a4 != -1 && a4 < sad_entry->field_1C) { sad_entry->field_1C = a4; } } return 0; } // 0x416DFC static int anim_move(Object* obj, int tile, int elev, int a3, int anim, int a5, int animationSequenceIndex) { if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) { return -1; } AnimationSad* sad_entry = &(sad[curr_sad]); sad_entry->obj = obj; if (a5) { sad_entry->flags = ANIM_SAD_0x20; } else { sad_entry->flags = 0; } sad_entry->field_20 = -2000; sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); sad_entry->animationTimestamp = 0; sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid); sad_entry->field_24 = tile; sad_entry->animationSequenceIndex = animationSequenceIndex; sad_entry->anim = anim; sad_entry->field_1C = make_path(obj, obj->tile, tile, sad_entry->rotations, a5); if (sad_entry->field_1C == 0) { sad_entry->field_20 = -1000; return -1; } if (a3 != -1 && sad_entry->field_1C > a3) { sad_entry->field_1C = a3; } return curr_sad++; } // 0x416F54 static int anim_move_straight_to_tile(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex, int flags) { if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) { return -1; } AnimationSad* sad_entry = &(sad[curr_sad]); sad_entry->obj = obj; sad_entry->flags = flags | ANIM_SAD_STRAIGHT; if (anim == -1) { sad_entry->fid = obj->fid; sad_entry->flags |= ANIM_SAD_NO_ANIM; } else { sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); } sad_entry->field_20 = -2000; sad_entry->animationTimestamp = 0; sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid); sad_entry->animationSequenceIndex = animationSequenceIndex; int v15; if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) { if (FID_ANIM_TYPE(obj->fid) == ANIM_JUMP_BEGIN) v15 = 16; else v15 = 4; } else { v15 = 32; } sad_entry->field_1C = make_straight_path(obj, obj->tile, tile, sad_entry->field_28, NULL, v15); if (sad_entry->field_1C == 0) { sad_entry->field_20 = -1000; return -1; } curr_sad++; return 0; } // 0x41712C int anim_move_on_stairs(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex) { if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) { return -1; } AnimationSad* sad_entry = &(sad[curr_sad]); sad_entry->flags = ANIM_SAD_STRAIGHT; sad_entry->obj = obj; if (anim == -1) { sad_entry->fid = obj->fid; sad_entry->flags |= ANIM_SAD_NO_ANIM; } else { sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); } sad_entry->field_20 = -2000; sad_entry->animationTimestamp = 0; sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid); sad_entry->animationSequenceIndex = animationSequenceIndex; sad_entry->field_1C = make_stair_path(obj, obj->tile, obj->elevation, tile, elevation, sad_entry->field_28, NULL); if (sad_entry->field_1C == 0) { sad_entry->field_20 = -1000; return -1; } curr_sad++; return 0; } // 0x417248 int check_for_falling(Object* obj, int anim, int a3) { if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) { return -1; } if (check_gravity(obj->tile, obj->elevation) == obj->elevation) { return -1; } AnimationSad* sad_entry = &(sad[curr_sad]); sad_entry->flags = ANIM_SAD_STRAIGHT; sad_entry->obj = obj; if (anim == -1) { sad_entry->fid = obj->fid; sad_entry->flags |= ANIM_SAD_NO_ANIM; } else { sad_entry->fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); } sad_entry->field_20 = -2000; sad_entry->animationTimestamp = 0; sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid); sad_entry->animationSequenceIndex = a3; sad_entry->field_1C = make_straight_path_func(obj, obj->tile, obj->tile, sad_entry->field_28, 0, 16, obj_blocking_at); if (sad_entry->field_1C == 0) { sad_entry->field_20 = -1000; return -1; } curr_sad++; return 0; } // 0x417360 static void object_move(int index) { AnimationSad* sad_entry = &(sad[index]); Object* object = sad_entry->obj; Rect dirty; Rect temp; if (sad_entry->field_20 == -2000) { obj_move_to_tile(object, object->tile, object->elevation, &dirty); obj_set_frame(object, 0, &temp); rect_min_bound(&dirty, &temp, &dirty); obj_set_rotation(object, sad_entry->rotations[0], &temp); rect_min_bound(&dirty, &temp, &dirty); int fid = art_id(FID_TYPE(object->fid), object->fid & 0xFFF, sad_entry->anim, (object->fid & 0xF000) >> 12, object->rotation + 1); obj_change_fid(object, fid, &temp); rect_min_bound(&dirty, &temp, &dirty); sad_entry->field_20 = 0; } else { obj_inc_frame(object, &dirty); } int frameX; int frameY; CacheEntry* cacheHandle; Art* art = art_ptr_lock(object->fid, &cacheHandle); if (art != NULL) { art_frame_hot(art, object->frame, object->rotation, &frameX, &frameY); art_ptr_unlock(cacheHandle); } else { frameX = 0; frameY = 0; } obj_offset(object, frameX, frameY, &temp); rect_min_bound(&dirty, &temp, &dirty); int rotation = sad_entry->rotations[sad_entry->field_20]; int y = off_tile[1][rotation]; int x = off_tile[0][rotation]; if ((x > 0 && x <= object->x) || (x < 0 && x >= object->x) || (y > 0 && y <= object->y) || (y < 0 && y >= object->y)) { x = object->x - x; y = object->y - y; int v10 = tile_num_in_direction(object->tile, rotation, 1); Object* v12 = obj_blocking_at(object, v10, object->elevation); if (v12 != NULL) { if (!anim_can_use_door(object, v12)) { sad_entry->field_1C = make_path(object, object->tile, sad_entry->field_24, sad_entry->rotations, 1); if (sad_entry->field_1C != 0) { obj_move_to_tile(object, object->tile, object->elevation, &temp); rect_min_bound(&dirty, &temp, &dirty); obj_set_frame(object, 0, &temp); rect_min_bound(&dirty, &temp, &dirty); obj_set_rotation(object, sad_entry->rotations[0], &temp); rect_min_bound(&dirty, &temp, &dirty); sad_entry->field_20 = 0; } else { sad_entry->field_20 = -1000; } v10 = -1; } else { obj_use_door(object, v12, 0); } } if (v10 != -1) { obj_move_to_tile(object, v10, object->elevation, &temp); rect_min_bound(&dirty, &temp, &dirty); int v17 = 0; if (isInCombat() && FID_TYPE(object->fid) == OBJ_TYPE_CRITTER) { int v18 = critter_compute_ap_from_distance(object, 1); if (combat_free_move < v18) { int ap = object->data.critter.combat.ap; int v20 = v18 - combat_free_move; combat_free_move = 0; if (v20 > ap) { object->data.critter.combat.ap = 0; } else { object->data.critter.combat.ap = ap - v20; } } else { combat_free_move -= v18; } if (object == obj_dude) { intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } v17 = (object->data.critter.combat.ap + combat_free_move) <= 0; } sad_entry->field_20 += 1; if (sad_entry->field_20 == sad_entry->field_1C || v17) { sad_entry->field_20 = -1000; } else { obj_set_rotation(object, sad_entry->rotations[sad_entry->field_20], &temp); rect_min_bound(&dirty, &temp, &dirty); obj_offset(object, x, y, &temp); rect_min_bound(&dirty, &temp, &dirty); } } } tile_refresh_rect(&dirty, object->elevation); if (sad_entry->field_20 == -1000) { anim_set_continue(sad_entry->animationSequenceIndex, 1); } } // 0x4177C0 static void object_straight_move(int index) { AnimationSad* sad_entry = &(sad[index]); Object* object = sad_entry->obj; Rect dirtyRect; Rect temp; if (sad_entry->field_20 == -2000) { obj_change_fid(object, sad_entry->fid, &dirtyRect); sad_entry->field_20 = 0; } else { obj_bound(object, &dirtyRect); } CacheEntry* cacheHandle; Art* art = art_ptr_lock(object->fid, &cacheHandle); if (art != NULL) { int lastFrame = art_frame_max_frame(art) - 1; art_ptr_unlock(cacheHandle); if ((sad_entry->flags & ANIM_SAD_NO_ANIM) == 0) { if ((sad_entry->flags & ANIM_SAD_WAIT_FOR_COMPLETION) == 0 || object->frame < lastFrame) { obj_inc_frame(object, &temp); rect_min_bound(&dirtyRect, &temp, &dirtyRect); } } if (sad_entry->field_20 < sad_entry->field_1C) { StraightPathNode* v12 = &(sad_entry->field_28[sad_entry->field_20]); obj_move_to_tile(object, v12->tile, v12->elevation, &temp); rect_min_bound(&dirtyRect, &temp, &dirtyRect); obj_offset(object, v12->x, v12->y, &temp); rect_min_bound(&dirtyRect, &temp, &dirtyRect); sad_entry->field_20++; } if (sad_entry->field_20 == sad_entry->field_1C) { if ((sad_entry->flags & ANIM_SAD_WAIT_FOR_COMPLETION) == 0 || object->frame == lastFrame) { sad_entry->field_20 = -1000; } } tile_refresh_rect(&dirtyRect, sad_entry->obj->elevation); if (sad_entry->field_20 == -1000) { anim_set_continue(sad_entry->animationSequenceIndex, 1); } } } // 0x4179B8 static int anim_animate(Object* obj, int anim, int animationSequenceIndex, int flags) { if (curr_sad == ANIMATION_SAD_LIST_CAPACITY) { return -1; } AnimationSad* sad_entry = &(sad[curr_sad]); int fid; if (anim == ANIM_TAKE_OUT) { sad_entry->flags = 0; fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_TAKE_OUT, flags, obj->rotation + 1); } else { sad_entry->flags = flags; fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); } if (!art_exists(fid)) { return -1; } sad_entry->obj = obj; sad_entry->fid = fid; sad_entry->animationSequenceIndex = animationSequenceIndex; sad_entry->animationTimestamp = 0; sad_entry->ticksPerFrame = compute_tpf(obj, sad_entry->fid); sad_entry->field_20 = 0; sad_entry->field_1C = 0; curr_sad++; return 0; } // 0x417B30 void object_animate() { if (curr_sad == 0) { return; } anim_in_bk = 1; for (int index = 0; index < curr_sad; index++) { AnimationSad* sad_entry = &(sad[index]); if (sad_entry->field_20 == -1000) { continue; } Object* object = sad_entry->obj; unsigned int time = get_time(); if (elapsed_tocks(time, sad_entry->animationTimestamp) < sad_entry->ticksPerFrame) { continue; } sad_entry->animationTimestamp = time; if (anim_set_check(sad_entry->animationSequenceIndex) == -1) { continue; } if (sad_entry->field_1C > 0) { if ((sad_entry->flags & ANIM_SAD_STRAIGHT) != 0) { object_straight_move(index); } else { int savedTile = object->tile; object_move(index); if (savedTile != object->tile) { scr_chk_spatials_in(object, object->tile, object->elevation); } } continue; } if (sad_entry->field_20 == 0) { for (int index = 0; index < curr_sad; index++) { AnimationSad* otherSad = &(sad[index]); if (object == otherSad->obj && otherSad->field_20 == -2000) { otherSad->field_20 = -1000; anim_set_continue(otherSad->animationSequenceIndex, 1); } } sad_entry->field_20 = -2000; } Rect dirtyRect; Rect tempRect; obj_bound(object, &dirtyRect); if (object->fid == sad_entry->fid) { if ((sad_entry->flags & ANIM_SAD_REVERSE) == 0) { CacheEntry* cacheHandle; Art* art = art_ptr_lock(object->fid, &cacheHandle); if (art != NULL) { if ((sad_entry->flags & ANIM_SAD_FOREVER) == 0 && object->frame == art_frame_max_frame(art) - 1) { sad_entry->field_20 = -1000; art_ptr_unlock(cacheHandle); if ((sad_entry->flags & ANIM_SAD_HIDE_ON_END) != 0) { // NOTE: Uninline. anim_hide(object, -1); } anim_set_continue(sad_entry->animationSequenceIndex, 1); continue; } else { obj_inc_frame(object, &tempRect); rect_min_bound(&dirtyRect, &tempRect, &dirtyRect); int frameX; int frameY; art_frame_hot(art, object->frame, object->rotation, &frameX, &frameY); obj_offset(object, frameX, frameY, &tempRect); rect_min_bound(&dirtyRect, &tempRect, &dirtyRect); art_ptr_unlock(cacheHandle); } } tile_refresh_rect(&dirtyRect, map_elevation); continue; } if ((sad_entry->flags & ANIM_SAD_FOREVER) != 0 || object->frame != 0) { int x; int y; CacheEntry* cacheHandle; Art* art = art_ptr_lock(object->fid, &cacheHandle); if (art != NULL) { art_frame_hot(art, object->frame, object->rotation, &x, &y); art_ptr_unlock(cacheHandle); } obj_dec_frame(object, &tempRect); rect_min_bound(&dirtyRect, &tempRect, &dirtyRect); obj_offset(object, -x, -y, &tempRect); rect_min_bound(&dirtyRect, &tempRect, &dirtyRect); tile_refresh_rect(&dirtyRect, map_elevation); continue; } sad_entry->field_20 = -1000; anim_set_continue(sad_entry->animationSequenceIndex, 1); } else { int x; int y; CacheEntry* cacheHandle; Art* art = art_ptr_lock(object->fid, &cacheHandle); if (art != NULL) { art_frame_offset(art, object->rotation, &x, &y); art_ptr_unlock(cacheHandle); } else { x = 0; y = 0; } Rect v29; obj_change_fid(object, sad_entry->fid, &v29); rect_min_bound(&dirtyRect, &v29, &dirtyRect); art = art_ptr_lock(object->fid, &cacheHandle); if (art != NULL) { int frame; if ((sad_entry->flags & ANIM_SAD_REVERSE) != 0) { frame = art_frame_max_frame(art) - 1; } else { frame = 0; } obj_set_frame(object, frame, &v29); rect_min_bound(&dirtyRect, &v29, &dirtyRect); int frameX; int frameY; art_frame_hot(art, object->frame, object->rotation, &frameX, &frameY); Rect v19; obj_offset(object, x + frameX, y + frameY, &v19); rect_min_bound(&dirtyRect, &v19, &dirtyRect); art_ptr_unlock(cacheHandle); } else { obj_set_frame(object, 0, &v29); rect_min_bound(&dirtyRect, &v29, &dirtyRect); } tile_refresh_rect(&dirtyRect, map_elevation); } } anim_in_bk = 0; object_anim_compact(); } // 0x417F18 static void object_anim_compact() { for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { AnimationSequence* animationSequence = &(anim_set[index]); if ((animationSequence->flags & ANIM_SEQ_0x20) != 0) { animationSequence->flags = 0; } } int index = 0; for (; index < curr_sad; index++) { if (sad[index].field_20 == -1000) { int v2 = index + 1; for (; v2 < curr_sad; v2++) { if (sad[v2].field_20 != -1000) { break; } } if (v2 == curr_sad) { break; } if (index != v2) { memcpy(&(sad[index]), &(sad[v2]), sizeof(AnimationSad)); sad[v2].field_20 = -1000; sad[v2].flags = 0; } } } curr_sad = index; } // 0x417FFC int check_move(int* a1) { int x; int y; mouse_get_position(&x, &y); int tile = tile_num(x, y, map_elevation); if (tile == -1) { return -1; } if (isInCombat()) { if (*a1 != -1) { if (keys[DIK_LCONTROL] || keys[DIK_RCONTROL]) { int hitMode; bool aiming; intface_get_attack(&hitMode, &aiming); int v6 = item_mp_cost(obj_dude, hitMode, aiming); *a1 = *a1 - v6; if (*a1 <= 0) { return -1; } } } } else { bool interruptWalk; configGetBool(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, &interruptWalk); if (interruptWalk) { register_clear(obj_dude); } } return tile; } // 0x4180B4 int dude_move(int a1) { // 0x51072C static int lastDest = -2; int v1; int tile = check_move(&v1); if (tile == -1) { return -1; } if (lastDest == tile) { return dude_run(a1); } lastDest = tile; register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_tile(obj_dude, tile, obj_dude->elevation, a1, 0); return register_end(); } // 0x41810C int dude_run(int a1) { int a4; int tile_num; a4 = a1; tile_num = check_move(&a4); if (tile_num == -1) { return -1; } if (!perk_level(obj_dude, PERK_SILENT_RUNNING)) { pc_flag_off(DUDE_STATE_SNEAKING); } register_begin(ANIMATION_REQUEST_RESERVED); register_object_run_to_tile(obj_dude, tile_num, obj_dude->elevation, a4, 0); return register_end(); } // 0x418168 void dude_fidget() { // 0x510730 static unsigned int last_time = 0; // 0x510734 static unsigned int next_time = 0; // 0x56C7E0 static Object* fidget_ptr[100]; if (game_user_wants_to_quit != 0) { return; } if (isInCombat()) { return; } if (vcr_status() != VCR_STATE_TURNED_OFF) { return; } if ((obj_dude->flags & OBJECT_HIDDEN) != 0) { return; } unsigned int v0 = get_bk_time(); if (elapsed_tocks(v0, last_time) <= next_time) { return; } last_time = v0; int v5 = 0; Object* object = obj_find_first_at(obj_dude->elevation); while (object != NULL) { if (v5 >= 100) { break; } if ((object->flags & OBJECT_HIDDEN) == 0 && FID_TYPE(object->fid) == OBJ_TYPE_CRITTER && FID_ANIM_TYPE(object->fid) == ANIM_STAND && !critter_is_dead(object)) { Rect rect; obj_bound(object, &rect); Rect intersection; if (rect_inside_bound(&rect, &scr_size, &intersection) == 0 && (map_data.field_34 != 97 || object->pid != 0x10000FA)) { fidget_ptr[v5++] = object; } } object = obj_find_next_at(); } int v13; if (v5 != 0) { int r = roll_random(0, v5 - 1); Object* object = fidget_ptr[r]; register_begin(ANIMATION_REQUEST_UNRESERVED | ANIMATION_REQUEST_INSIGNIFICANT); bool v8 = false; if (object == obj_dude) { v8 = true; } else { char v15[16]; v15[0] = '\0'; art_get_base_name(1, object->fid & 0xFFF, v15); if (v15[0] == 'm' || v15[0] == 'M') { if (obj_dist(object, obj_dude) < critterGetStat(obj_dude, STAT_PERCEPTION) * 2) { v8 = true; } } } if (v8) { const char* sfx = gsnd_build_character_sfx_name(object, ANIM_STAND, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(object, sfx, 0); } register_object_animate(object, ANIM_STAND, 0); register_end(); v13 = 20 / v5; } else { v13 = 7; } if (v13 < 1) { v13 = 1; } else if (v13 > 7) { v13 = 7; } next_time = roll_random(0, 3000) + 1000 * v13; } // 0x418378 void dude_stand(Object* obj, int rotation, int fid) { Rect rect; obj_set_rotation(obj, rotation, &rect); int x = 0; int y = 0; int weaponAnimationCode = (obj->fid & 0xF000) >> 12; if (weaponAnimationCode != 0) { if (fid == -1) { int takeOutFid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_TAKE_OUT, weaponAnimationCode, obj->rotation + 1); CacheEntry* takeOutFrmHandle; Art* takeOutFrm = art_ptr_lock(takeOutFid, &takeOutFrmHandle); if (takeOutFrm != NULL) { int frameCount = art_frame_max_frame(takeOutFrm); for (int frame = 0; frame < frameCount; frame++) { int offsetX; int offsetY; art_frame_hot(takeOutFrm, frame, obj->rotation, &offsetX, &offsetY); x += offsetX; y += offsetY; } art_ptr_unlock(takeOutFrmHandle); CacheEntry* standFrmHandle; int standFid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_STAND, 0, obj->rotation + 1); Art* standFrm = art_ptr_lock(standFid, &standFrmHandle); if (standFrm != NULL) { int offsetX; int offsetY; if (art_frame_offset(standFrm, obj->rotation, &offsetX, &offsetY) == 0) { x += offsetX; y += offsetY; } art_ptr_unlock(standFrmHandle); } } } } if (fid == -1) { int anim; if (FID_ANIM_TYPE(obj->fid) == ANIM_FIRE_DANCE) { anim = ANIM_FIRE_DANCE; } else { anim = ANIM_STAND; } fid = art_id(FID_TYPE(obj->fid), (obj->fid & 0xFFF), anim, (obj->fid & 0xF000) >> 12, obj->rotation + 1); } Rect temp; obj_change_fid(obj, fid, &temp); rect_min_bound(&rect, &temp, &rect); obj_move_to_tile(obj, obj->tile, obj->elevation, &temp); rect_min_bound(&rect, &temp, &rect); obj_set_frame(obj, 0, &temp); rect_min_bound(&rect, &temp, &rect); obj_offset(obj, x, y, &temp); rect_min_bound(&rect, &temp, &rect); tile_refresh_rect(&rect, obj->elevation); } // 0x418574 void dude_standup(Object* a1) { register_begin(ANIMATION_REQUEST_RESERVED); int anim; if (FID_ANIM_TYPE(a1->fid) == ANIM_FALL_BACK) { anim = ANIM_BACK_TO_STANDING; } else { anim = ANIM_PRONE_TO_STANDING; } register_object_animate(a1, anim, 0); register_end(); a1->data.critter.combat.results &= ~DAM_KNOCKED_DOWN; } // 0x4185EC static int anim_turn_towards(Object* obj, int delta, int animationSequenceIndex) { if (!critter_is_prone(obj)) { int rotation = obj->rotation + delta; if (rotation >= ROTATION_COUNT) { rotation = ROTATION_NE; } else if (rotation < 0) { rotation = ROTATION_NW; } dude_stand(obj, rotation, -1); } anim_set_continue(animationSequenceIndex, 0); return 0; } // NOTE: Inlined. // // 0x41862C int anim_hide(Object* object, int animationSequenceIndex) { Rect rect; if (obj_turn_off(object, &rect) == 0) { tile_refresh_rect(&rect, object->elevation); } if (animationSequenceIndex != -1) { anim_set_continue(animationSequenceIndex, 0); } return 0; } // 0x418660 int anim_change_fid(Object* obj, int animationSequenceIndex, int fid) { Rect rect; Rect v7; if (FID_ANIM_TYPE(fid)) { obj_change_fid(obj, fid, &rect); obj_set_frame(obj, 0, &v7); rect_min_bound(&rect, &v7, &rect); tile_refresh_rect(&rect, obj->elevation); } else { dude_stand(obj, obj->rotation, fid); } anim_set_continue(animationSequenceIndex, 0); return 0; } // 0x4186CC void anim_stop() { anim_in_anim_stop = true; curr_anim_set = -1; for (int index = 0; index < ANIMATION_SEQUENCE_LIST_CAPACITY; index++) { anim_set_end(index); } anim_in_anim_stop = false; curr_sad = 0; } // 0x418708 static int check_gravity(int tile, int elevation) { for (; elevation > 0; elevation--) { int x; int y; tile_coord(tile, &x, &y, elevation); int squareTile = square_num(x + 2, y + 8, elevation); int fid = art_id(OBJ_TYPE_TILE, square[elevation]->field_0[squareTile] & 0xFFF, 0, 0, 0); if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { break; } } return elevation; } // 0x418794 unsigned int compute_tpf(Object* object, int fid) { int fps; CacheEntry* handle; Art* frm = art_ptr_lock(fid, &handle); if (frm != NULL) { fps = art_frame_fps(frm); art_ptr_unlock(handle); } else { fps = 10; } if (isInCombat()) { if (FID_ANIM_TYPE(fid) == ANIM_WALK) { int playerSpeedup = 0; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, &playerSpeedup); if (object != obj_dude || playerSpeedup == 1) { int combatSpeed = 0; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, &combatSpeed); fps += combatSpeed; } } } return 1000 / fps; } ================================================ FILE: src/game/anim.h ================================================ #ifndef ANIMATION_H #define ANIMATION_H #include #include "game/object_types.h" typedef enum AnimationRequestOptions { ANIMATION_REQUEST_UNRESERVED = 0x01, ANIMATION_REQUEST_RESERVED = 0x02, ANIMATION_REQUEST_NO_STAND = 0x04, ANIMATION_REQUEST_0x100 = 0x100, ANIMATION_REQUEST_INSIGNIFICANT = 0x200, } AnimationRequestOptions; // Basic animations: 0-19 // Knockdown and death: 20-35 // Change positions: 36-37 // Weapon: 38-47 // Single-frame death animations (the last frame of knockdown and death animations): 48-63 typedef enum AnimationType { ANIM_STAND = 0, ANIM_WALK = 1, ANIM_JUMP_BEGIN = 2, ANIM_JUMP_END = 3, ANIM_CLIMB_LADDER = 4, ANIM_FALLING = 5, ANIM_UP_STAIRS_RIGHT = 6, ANIM_UP_STAIRS_LEFT = 7, ANIM_DOWN_STAIRS_RIGHT = 8, ANIM_DOWN_STAIRS_LEFT = 9, ANIM_MAGIC_HANDS_GROUND = 10, ANIM_MAGIC_HANDS_MIDDLE = 11, ANIM_MAGIC_HANDS_UP = 12, ANIM_DODGE_ANIM = 13, ANIM_HIT_FROM_FRONT = 14, ANIM_HIT_FROM_BACK = 15, ANIM_THROW_PUNCH = 16, ANIM_KICK_LEG = 17, ANIM_THROW_ANIM = 18, ANIM_RUNNING = 19, ANIM_FALL_BACK = 20, ANIM_FALL_FRONT = 21, ANIM_BAD_LANDING = 22, ANIM_BIG_HOLE = 23, ANIM_CHARRED_BODY = 24, ANIM_CHUNKS_OF_FLESH = 25, ANIM_DANCING_AUTOFIRE = 26, ANIM_ELECTRIFY = 27, ANIM_SLICED_IN_HALF = 28, ANIM_BURNED_TO_NOTHING = 29, ANIM_ELECTRIFIED_TO_NOTHING = 30, ANIM_EXPLODED_TO_NOTHING = 31, ANIM_MELTED_TO_NOTHING = 32, ANIM_FIRE_DANCE = 33, ANIM_FALL_BACK_BLOOD = 34, ANIM_FALL_FRONT_BLOOD = 35, ANIM_PRONE_TO_STANDING = 36, ANIM_BACK_TO_STANDING = 37, ANIM_TAKE_OUT = 38, ANIM_PUT_AWAY = 39, ANIM_PARRY_ANIM = 40, ANIM_THRUST_ANIM = 41, ANIM_SWING_ANIM = 42, ANIM_POINT = 43, ANIM_UNPOINT = 44, ANIM_FIRE_SINGLE = 45, ANIM_FIRE_BURST = 46, ANIM_FIRE_CONTINUOUS = 47, ANIM_FALL_BACK_SF = 48, ANIM_FALL_FRONT_SF = 49, ANIM_BAD_LANDING_SF = 50, ANIM_BIG_HOLE_SF = 51, ANIM_CHARRED_BODY_SF = 52, ANIM_CHUNKS_OF_FLESH_SF = 53, ANIM_DANCING_AUTOFIRE_SF = 54, ANIM_ELECTRIFY_SF = 55, ANIM_SLICED_IN_HALF_SF = 56, ANIM_BURNED_TO_NOTHING_SF = 57, ANIM_ELECTRIFIED_TO_NOTHING_SF = 58, ANIM_EXPLODED_TO_NOTHING_SF = 59, ANIM_MELTED_TO_NOTHING_SF = 60, ANIM_FIRE_DANCE_SF = 61, ANIM_FALL_BACK_BLOOD_SF = 62, ANIM_FALL_FRONT_BLOOD_SF = 63, ANIM_CALLED_SHOT_PIC = 64, ANIM_COUNT = 65, FIRST_KNOCKDOWN_AND_DEATH_ANIM = ANIM_FALL_BACK, LAST_KNOCKDOWN_AND_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD, FIRST_SF_DEATH_ANIM = ANIM_FALL_BACK_SF, LAST_SF_DEATH_ANIM = ANIM_FALL_FRONT_BLOOD_SF, } AnimationType; #define FID_ANIM_TYPE(value) ((value) & 0xFF0000) >> 16 // Signature of animation callback accepting 2 parameters. typedef int AnimationCallback(void*, void*); // Signature of animation callback accepting 3 parameters. typedef int AnimationCallback3(void*, void*, void*); typedef Object* PathBuilderCallback(Object* object, int tile, int elevation); typedef struct StraightPathNode { int tile; int elevation; int x; int y; } StraightPathNode; void anim_init(); void anim_reset(); void anim_exit(); int register_begin(int a1); int register_priority(int a1); int register_clear(Object* a1); int register_end(); int check_registry(Object* obj); int anim_busy(Object* a1); int register_object_move_to_object(Object* owner, Object* destination, int actionPoints, int delay); int register_object_run_to_object(Object* owner, Object* destination, int actionPoints, int delay); int register_object_move_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay); int register_object_run_to_tile(Object* owner, int tile, int elevation, int actionPoints, int delay); int register_object_move_straight_to_tile(Object* object, int tile, int elevation, int anim, int delay); int register_object_animate_and_move_straight(Object* owner, int tile, int elev, int anim, int delay); int register_object_move_on_stairs(Object* owner, Object* stairs, int delay); int register_object_check_falling(Object* owner, int delay); int register_object_animate(Object* owner, int anim, int delay); int register_object_animate_reverse(Object* owner, int anim, int delay); int register_object_animate_and_hide(Object* owner, int anim, int delay); int register_object_turn_towards(Object* owner, int tile); int register_object_inc_rotation(Object* owner); int register_object_dec_rotation(Object* owner); int register_object_erase(Object* object); int register_object_must_erase(Object* object); int register_object_call(void* a1, void* a2, AnimationCallback* proc, int delay); int register_object_call3(void* a1, void* a2, void* a3, AnimationCallback3* proc, int delay); int register_object_must_call(void* a1, void* a2, AnimationCallback* proc, int delay); int register_object_fset(Object* object, int flag, int delay); int register_object_funset(Object* object, int flag, int delay); int register_object_change_fid(Object* owner, int fid, int delay); int register_object_take_out(Object* owner, int weaponAnimationCode, int delay); int register_object_light(Object* owner, int lightDistance, int delay); int register_object_outline(Object* object, bool outline, int delay); int register_object_play_sfx(Object* owner, const char* soundEffectName, int delay); int register_object_animate_forever(Object* owner, int anim, int delay); int register_ping(int a1, int a2); int make_path(Object* object, int from, int to, unsigned char* a4, int a5); int make_path_func(Object* object, int from, int to, unsigned char* rotations, int a5, PathBuilderCallback* callback); int idist(int a1, int a2, int a3, int a4); int EST(int tile1, int tile2); int make_straight_path(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6); int make_straight_path_func(Object* a1, int from, int to, StraightPathNode* pathNodes, Object** a5, int a6, PathBuilderCallback* callback); int anim_move_on_stairs(Object* obj, int tile, int elevation, int anim, int animationSequenceIndex); int check_for_falling(Object* obj, int anim, int a3); void object_animate(); int check_move(int* a1); int dude_move(int a1); int dude_run(int a1); void dude_fidget(); void dude_stand(Object* obj, int rotation, int fid); void dude_standup(Object* a1); int anim_hide(Object* object, int animationSequenceIndex); int anim_change_fid(Object* obj, int animationSequenceIndex, int fid); void anim_stop(); unsigned int compute_tpf(Object* object, int fid); #endif /* ANIMATION_H */ ================================================ FILE: src/game/art.c ================================================ #include "game/art.h" #include #include #include #include "game/anim.h" #include "game/artload.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/proto.h" #define WIN32_LEAN_AND_MEAN #include // 0x510738 static ArtListDescription art[OBJ_TYPE_COUNT] = { { 0, "items", 0, 0, 0 }, { 0, "critters", 0, 0, 0 }, { 0, "scenery", 0, 0, 0 }, { 0, "walls", 0, 0, 0 }, { 0, "tiles", 0, 0, 0 }, { 0, "misc", 0, 0, 0 }, { 0, "intrface", 0, 0, 0 }, { 0, "inven", 0, 0, 0 }, { 0, "heads", 0, 0, 0 }, { 0, "backgrnd", 0, 0, 0 }, { 0, "skilldex", 0, 0, 0 }, }; // This flag denotes that localized arts should be looked up first. Used // together with [darn_foreign_sub_path]. // // 0x510898 static bool darn_foreigners = false; // 0x51089C static const char* head1 = "gggnnnbbbgnb"; // 0x5108A0 static const char* head2 = "vfngfbnfvppp"; // Current native look base fid. // // 0x5108A4 int art_vault_guy_num = 0; // Base fids for unarmored dude. // // Outfit file names: // - tribal: "hmwarr", "hfprim" // - jumpsuit: "hmjmps", "hfjmps" // // NOTE: This value could have been done with two separate arrays - one for // tribal look, and one for jumpsuit look. However in this case it would have // been accessed differently in 0x49F984, which clearly uses look type as an // index, not gender. // // 0x5108A8 int art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT]; // Index of "grid001.frm" in tiles.lst. // // 0x5108B8 int art_mapper_blank_tile = 1; // Non-english language name. // // This value is used as a directory name to display localized arts. // // 0x56C970 static char darn_foreign_sub_path[32]; // 0x56C990 Cache art_cache; // 0x56C9E4 static char art_name[MAX_PATH]; // 0x56CAE8 HeadDescription* head_info; // 0x56CAEC static int* anon_alias; // 0x56CAF0 static int* artCritterFidShouldRunData; // 0x418840 int art_init() { char path[MAX_PATH]; File* stream; char string[200]; int cacheSize; if (!config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, &cacheSize)) { cacheSize = 8; } if (!cache_init(&art_cache, art_data_size, art_data_load, art_data_free, cacheSize << 20)) { debug_printf("cache_init failed in art_init\n"); return -1; } char* language; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language) && stricmp(language, ENGLISH) != 0) { strcpy(darn_foreign_sub_path, language); darn_foreigners = true; } bool critterDbSelected = false; for (int objectType = 0; objectType < OBJ_TYPE_COUNT; objectType++) { art[objectType].flags = 0; sprintf(path, "%s%s%s\\%s.lst", cd_path_base, "art\\", art[objectType].name, art[objectType].name); int oldDb; if (objectType == OBJ_TYPE_CRITTER) { oldDb = db_current(); critterDbSelected = true; db_select(critter_db_handle); } if (art_read_lst(path, &(art[objectType].fileNames), &(art[objectType].fileNamesLength)) != 0) { debug_printf("art_read_lst failed in art_init\n"); if (critterDbSelected) { db_select(oldDb); } cache_exit(&art_cache); return -1; } if (objectType == OBJ_TYPE_CRITTER) { critterDbSelected = false; db_select(oldDb); } } anon_alias = (int*)mem_malloc(sizeof(*anon_alias) * art[OBJ_TYPE_CRITTER].fileNamesLength); if (anon_alias == NULL) { art[OBJ_TYPE_CRITTER].fileNamesLength = 0; debug_printf("Out of memory for anon_alias in art_init\n"); cache_exit(&art_cache); return -1; } artCritterFidShouldRunData = (int*)mem_malloc(sizeof(*artCritterFidShouldRunData) * art[1].fileNamesLength); if (artCritterFidShouldRunData == NULL) { art[OBJ_TYPE_CRITTER].fileNamesLength = 0; debug_printf("Out of memory for artCritterFidShouldRunData in art_init\n"); cache_exit(&art_cache); return -1; } for (int critterIndex = 0; critterIndex < art[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) { artCritterFidShouldRunData[critterIndex] = 0; } sprintf(path, "%s%s%s\\%s.lst", cd_path_base, "art\\", art[OBJ_TYPE_CRITTER].name, art[OBJ_TYPE_CRITTER].name); stream = db_fopen(path, "rt"); if (stream == NULL) { debug_printf("Unable to open %s in art_init\n", path); cache_exit(&art_cache); return -1; } char* critterFileNames = art[OBJ_TYPE_CRITTER].fileNames; for (int critterIndex = 0; critterIndex < art[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) { if (stricmp(critterFileNames, "hmjmps") == 0) { art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_MALE] = critterIndex; } else if (stricmp(critterFileNames, "hfjmps") == 0) { art_vault_person_nums[DUDE_NATIVE_LOOK_JUMPSUIT][GENDER_FEMALE] = critterIndex; } if (stricmp(critterFileNames, "hmwarr") == 0) { art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_MALE] = critterIndex; art_vault_guy_num = critterIndex; } else if (stricmp(critterFileNames, "hfprim") == 0) { art_vault_person_nums[DUDE_NATIVE_LOOK_TRIBAL][GENDER_FEMALE] = critterIndex; } critterFileNames += 13; } for (int critterIndex = 0; critterIndex < art[OBJ_TYPE_CRITTER].fileNamesLength; critterIndex++) { if (!db_fgets(string, sizeof(string), stream)) { break; } char* sep1 = strchr(string, ','); if (sep1 != NULL) { anon_alias[critterIndex] = atoi(sep1 + 1); char* sep2 = strchr(sep1 + 1, ','); if (sep2 != NULL) { artCritterFidShouldRunData[critterIndex] = atoi(sep2 + 1); } else { artCritterFidShouldRunData[critterIndex] = 0; } } else { anon_alias[critterIndex] = art_vault_guy_num; artCritterFidShouldRunData[critterIndex] = 1; } } db_fclose(stream); char* tileFileNames = art[OBJ_TYPE_TILE].fileNames; for (int tileIndex = 0; tileIndex < art[OBJ_TYPE_TILE].fileNamesLength; tileIndex++) { if (stricmp(tileFileNames, "grid001.frm") == 0) { art_mapper_blank_tile = tileIndex; } tileFileNames += 13; } head_info = (HeadDescription*)mem_malloc(sizeof(*head_info) * art[OBJ_TYPE_HEAD].fileNamesLength); if (head_info == NULL) { art[OBJ_TYPE_HEAD].fileNamesLength = 0; debug_printf("Out of memory for head_info in art_init\n"); cache_exit(&art_cache); return -1; } sprintf(path, "%s%s%s\\%s.lst", cd_path_base, "art\\", art[OBJ_TYPE_HEAD].name, art[OBJ_TYPE_HEAD].name); stream = db_fopen(path, "rt"); if (stream == NULL) { debug_printf("Unable to open %s in art_init\n", path); cache_exit(&art_cache); return -1; } for (int headIndex = 0; headIndex < art[OBJ_TYPE_HEAD].fileNamesLength; headIndex++) { if (!db_fgets(string, sizeof(string), stream)) { break; } char* sep1 = strchr(string, ','); if (sep1 != NULL) { *sep1 = '\0'; } else { sep1 = string; } char* sep2 = strchr(sep1, ','); if (sep2 != NULL) { *sep2 = '\0'; } else { sep2 = sep1; } head_info[headIndex].goodFidgetCount = atoi(sep1 + 1); char* sep3 = strchr(sep2, ','); if (sep3 != NULL) { *sep3 = '\0'; } else { sep3 = sep2; } head_info[headIndex].neutralFidgetCount = atoi(sep2 + 1); char* sep4 = strpbrk(sep3 + 1, " ,;\t\n"); if (sep4 != NULL) { *sep4 = '\0'; } head_info[headIndex].badFidgetCount = atoi(sep3 + 1); } db_fclose(stream); return 0; } // 0x418EB8 void art_reset() { } // 0x418EBC void art_exit() { cache_exit(&art_cache); mem_free(anon_alias); mem_free(artCritterFidShouldRunData); for (int index = 0; index < OBJ_TYPE_COUNT; index++) { mem_free(art[index].fileNames); art[index].fileNames = NULL; mem_free(art[index].field_18); art[index].field_18 = NULL; } mem_free(head_info); } // 0x418F1C char* art_dir(int objectType) { return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? art[objectType].name : NULL; } // 0x418F34 int art_get_disable(int objectType) { return objectType >= OBJ_TYPE_ITEM && objectType < OBJ_TYPE_COUNT ? art[objectType].flags & 1 : 0; } // NOTE: Unused. // // 0x418F50 void art_toggle_disable(int objectType) { if (objectType >= 0 && objectType < OBJ_TYPE_COUNT) { art[objectType].flags ^= 1; } } // NOTE: Unused. // // 0x418F64 int art_total(int objectType) { return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? art[objectType].fileNamesLength : 0; } // 0x418F7C int art_head_fidgets(int headFid) { if (FID_TYPE(headFid) != OBJ_TYPE_HEAD) { return 0; } int head = headFid & 0xFFF; if (head > art[OBJ_TYPE_HEAD].fileNamesLength) { return 0; } HeadDescription* headDescription = &(head_info[head]); int fidget = (headFid & 0xFF0000) >> 16; switch (fidget) { case FIDGET_GOOD: return headDescription->goodFidgetCount; case FIDGET_NEUTRAL: return headDescription->neutralFidgetCount; case FIDGET_BAD: return headDescription->badFidgetCount; } return 0; } // 0x418FFC void scale_art(int fid, unsigned char* dest, int width, int height, int pitch) { // NOTE: Original code is different. For unknown reason it directly calls // many art functions, for example instead of [art_ptr_lock] it calls lower level // [cache_lock], instead of [art_frame_width] is calls [frame_ptr], then get // width from frame's struct field. I don't know if this was intentional or // not. I've replaced these calls with higher level functions where // appropriate. CacheEntry* handle; Art* frm = art_ptr_lock(fid, &handle); if (frm == NULL) { return; } unsigned char* frameData = art_frame_data(frm, 0, 0); int frameWidth = art_frame_width(frm, 0, 0); int frameHeight = art_frame_length(frm, 0, 0); int remainingWidth = width - frameWidth; int remainingHeight = height - frameHeight; if (remainingWidth < 0 || remainingHeight < 0) { if (height * frameWidth >= width * frameHeight) { trans_cscale(frameData, frameWidth, frameHeight, frameWidth, dest + pitch * ((height - width * frameHeight / frameWidth) / 2), width, width * frameHeight / frameWidth, pitch); } else { trans_cscale(frameData, frameWidth, frameHeight, frameWidth, dest + (width - height * frameWidth / frameHeight) / 2, height * frameWidth / frameHeight, height, pitch); } } else { trans_buf_to_buf(frameData, frameWidth, frameHeight, frameWidth, dest + pitch * (remainingHeight / 2) + remainingWidth / 2, pitch); } art_ptr_unlock(handle); } // 0x419160 Art* art_ptr_lock(int fid, CacheEntry** handlePtr) { if (handlePtr == NULL) { return NULL; } Art* art = NULL; cache_lock(&art_cache, fid, (void**)&art, handlePtr); return art; } // 0x419188 unsigned char* art_ptr_lock_data(int fid, int frame, int direction, CacheEntry** handlePtr) { Art* art; ArtFrame* frm; art = NULL; if (handlePtr) { cache_lock(&art_cache, fid, (void**)&art, handlePtr); } if (art != NULL) { frm = frame_ptr(art, frame, direction); if (frm != NULL) { return (unsigned char*)frm + sizeof(*frm); } } return NULL; } // 0x4191CC unsigned char* art_lock(int fid, CacheEntry** handlePtr, int* widthPtr, int* heightPtr) { *handlePtr = NULL; Art* art; cache_lock(&art_cache, fid, (void**)&art, handlePtr); if (art == NULL) { return NULL; } // NOTE: Uninline. *widthPtr = art_frame_width(art, 0, 0); if (*widthPtr == -1) { return NULL; } // NOTE: Uninline. *heightPtr = art_frame_length(art, 0, 0); if (*heightPtr == -1) { return NULL; } // NOTE: Uninline. return art_frame_data(art, 0, 0); } // 0x419260 int art_ptr_unlock(CacheEntry* handle) { return cache_unlock(&art_cache, handle); } // 0x41927C int art_flush() { return cache_flush(&art_cache); } // NOTE: Unused. // // 0x419294 int art_discard(int fid) { if (cache_discard(&art_cache, fid) == 0) { return -1; } return 0; } // 0x4192B0 int art_get_base_name(int objectType, int id, char* dest) { ArtListDescription* ptr; if (objectType < OBJ_TYPE_ITEM && objectType >= OBJ_TYPE_COUNT) { return -1; } ptr = &(art[objectType]); if (id >= ptr->fileNamesLength) { return -1; } strcpy(dest, ptr->fileNames + id * 13); return 0; } // 0x419314 int art_get_code(int animation, int weaponType, char* a3, char* a4) { if (weaponType < 0 || weaponType >= WEAPON_ANIMATION_COUNT) { return -1; } if (animation >= ANIM_TAKE_OUT && animation <= ANIM_FIRE_CONTINUOUS) { *a4 = 'c' + (animation - ANIM_TAKE_OUT); if (weaponType == WEAPON_ANIMATION_NONE) { return -1; } *a3 = 'd' + (weaponType - 1); return 0; } else if (animation == ANIM_PRONE_TO_STANDING) { *a4 = 'h'; *a3 = 'c'; return 0; } else if (animation == ANIM_BACK_TO_STANDING) { *a4 = 'j'; *a3 = 'c'; return 0; } else if (animation == ANIM_CALLED_SHOT_PIC) { *a4 = 'a'; *a3 = 'n'; return 0; } else if (animation >= FIRST_SF_DEATH_ANIM) { *a4 = 'a' + (animation - FIRST_SF_DEATH_ANIM); *a3 = 'r'; return 0; } else if (animation >= FIRST_KNOCKDOWN_AND_DEATH_ANIM) { *a4 = 'a' + (animation - FIRST_KNOCKDOWN_AND_DEATH_ANIM); *a3 = 'b'; return 0; } else if (animation == ANIM_THROW_ANIM) { if (weaponType == WEAPON_ANIMATION_KNIFE) { // knife *a3 = 'd'; *a4 = 'm'; } else if (weaponType == WEAPON_ANIMATION_SPEAR) { // spear *a3 = 'g'; *a4 = 'm'; } else { // other -> probably rock or grenade *a3 = 'a'; *a4 = 's'; } return 0; } else if (animation == ANIM_DODGE_ANIM) { if (weaponType <= 0) { *a3 = 'a'; *a4 = 'n'; } else { *a3 = 'd' + (weaponType - 1); *a4 = 'e'; } return 0; } *a4 = 'a' + animation; if (animation <= ANIM_WALK && weaponType > 0) { *a3 = 'd' + (weaponType - 1); return 0; } *a3 = 'a'; return 0; } // 0x419428 char* art_get_name(int fid) { int v1, v2, v3, v4, v5, type, v8, v10; char v9, v11, v12; v2 = fid; v10 = (fid & 0x70000000) >> 28; v1 = art_alias_fid(fid); if (v1 != -1) { v2 = v1; } *art_name = '\0'; v3 = v2 & 0xFFF; v4 = FID_ANIM_TYPE(v2); v5 = (v2 & 0xF000) >> 12; type = FID_TYPE(v2); if (v3 >= art[type].fileNamesLength) { return NULL; } if (type < OBJ_TYPE_ITEM || type >= OBJ_TYPE_COUNT) { return NULL; } v8 = v3 * 13; if (type == 1) { if (art_get_code(v4, v5, &v11, &v12) == -1) { return NULL; } if (v10) { sprintf(art_name, "%s%s%s\\%s%c%c.fr%c", cd_path_base, "art\\", art[1].name, art[1].fileNames + v8, v11, v12, v10 + 47); } else { sprintf(art_name, "%s%s%s\\%s%c%c.frm", cd_path_base, "art\\", art[1].name, art[1].fileNames + v8, v11, v12); } } else if (type == 8) { v9 = head2[v4]; if (v9 == 'f') { sprintf(art_name, "%s%s%s\\%s%c%c%d.frm", cd_path_base, "art\\", art[8].name, art[8].fileNames + v8, head1[v4], 102, v5); } else { sprintf(art_name, "%s%s%s\\%s%c%c.frm", cd_path_base, "art\\", art[8].name, art[8].fileNames + v8, head1[v4], v9); } } else { sprintf(art_name, "%s%s%s\\%s", cd_path_base, "art\\", art[type].name, art[type].fileNames + v8); } return art_name; } // art_read_lst // 0x419664 int art_read_lst(const char* path, char** artListPtr, int* artListSizePtr) { File* stream = db_fopen(path, "rt"); if (stream == NULL) { return -1; } int count = 0; char string[200]; while (db_fgets(string, sizeof(string), stream)) { count++; } db_fseek(stream, 0, SEEK_SET); *artListSizePtr = count; char* artList = (char*)mem_malloc(13 * count); *artListPtr = artList; if (artList == NULL) { db_fclose(stream); return -1; } while (db_fgets(string, sizeof(string), stream)) { char* brk = strpbrk(string, " ,;\r\t\n"); if (brk != NULL) { *brk = '\0'; } strncpy(artList, string, 12); artList[12] = '\0'; artList += 13; } db_fclose(stream); return 0; } // 0x419760 int art_frame_fps(Art* art) { if (art == NULL) { return 10; } return art->framesPerSecond == 0 ? 10 : art->framesPerSecond; } // 0x419778 int art_frame_action_frame(Art* art) { return art == NULL ? -1 : art->actionFrame; } // 0x41978C int art_frame_max_frame(Art* art) { return art == NULL ? -1 : art->frameCount; } // 0x4197A0 int art_frame_width(Art* art, int frame, int direction) { ArtFrame* frm; frm = frame_ptr(art, frame, direction); if (frm == NULL) { return -1; } return frm->width; } // 0x4197B8 int art_frame_length(Art* art, int frame, int direction) { ArtFrame* frm; frm = frame_ptr(art, frame, direction); if (frm == NULL) { return -1; } return frm->height; } // 0x4197D4 int art_frame_width_length(Art* art, int frame, int direction, int* widthPtr, int* heightPtr) { ArtFrame* frm; frm = frame_ptr(art, frame, direction); if (frm == NULL) { if (widthPtr != NULL) { *widthPtr = 0; } if (heightPtr != NULL) { *heightPtr = 0; } return -1; } if (widthPtr != NULL) { *widthPtr = frm->width; } if (heightPtr != NULL) { *heightPtr = frm->height; } return 0; } // 0x419820 int art_frame_hot(Art* art, int frame, int direction, int* xPtr, int* yPtr) { ArtFrame* frm; frm = frame_ptr(art, frame, direction); if (frm == NULL) { return -1; } *xPtr = frm->x; *yPtr = frm->y; return 0; } // 0x41984C int art_frame_offset(Art* art, int rotation, int* xPtr, int* yPtr) { if (art == NULL) { return -1; } *xPtr = art->xOffsets[rotation]; *yPtr = art->yOffsets[rotation]; return 0; } // 0x419870 unsigned char* art_frame_data(Art* art, int frame, int direction) { ArtFrame* frm; frm = frame_ptr(art, frame, direction); if (frm == NULL) { return NULL; } return (unsigned char*)frm + sizeof(*frm); } // 0x419880 ArtFrame* frame_ptr(Art* art, int frame, int rotation) { if (rotation < 0 || rotation >= 6) { return NULL; } if (art == NULL) { return NULL; } if (frame < 0 || frame >= art->frameCount) { return NULL; } ArtFrame* frm = (ArtFrame*)((unsigned char*)art + sizeof(*art) + art->dataOffsets[rotation]); for (int index = 0; index < frame; index++) { frm = (ArtFrame*)((unsigned char*)frm + sizeof(*frm) + frm->size); } return frm; } // 0x4198C8 bool art_exists(int fid) { bool result = false; int oldDb = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { oldDb = db_current(); db_select(critter_db_handle); } char* filePath = art_get_name(fid); if (filePath != NULL) { int fileSize; if (db_dir_entry(filePath, &fileSize) != -1) { result = true; } } if (oldDb != -1) { db_select(oldDb); } return result; } // NOTE: Exactly the same implementation as `art_exists`. // // 0x419930 bool art_fid_valid(int fid) { bool result = false; int oldDb = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { oldDb = db_current(); db_select(critter_db_handle); } char* filePath = art_get_name(fid); if (filePath != NULL) { int fileSize; if (db_dir_entry(filePath, &fileSize) != -1) { result = true; } } if (oldDb != -1) { db_select(oldDb); } return result; } // 0x419998 int art_alias_num(int index) { return anon_alias[index]; } // 0x4199AC int artCritterFidShouldRun(int fid) { if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { return artCritterFidShouldRunData[fid & 0xFFF]; } return 0; } // 0x4199D4 int art_alias_fid(int fid) { int type = FID_TYPE(fid); int anim = FID_ANIM_TYPE(fid); if (type == OBJ_TYPE_CRITTER) { if (anim == ANIM_ELECTRIFY || anim == ANIM_BURNED_TO_NOTHING || anim == ANIM_ELECTRIFIED_TO_NOTHING || anim == ANIM_ELECTRIFY_SF || anim == ANIM_BURNED_TO_NOTHING_SF || anim == ANIM_ELECTRIFIED_TO_NOTHING_SF || anim == ANIM_FIRE_DANCE || anim == ANIM_CALLED_SHOT_PIC) { // NOTE: Original code is slightly different. It uses many mutually // mirrored bitwise operators. Probably result of some macros for // getting/setting individual bits on fid. return (fid & 0x70000000) | ((anim << 16) & 0xFF0000) | 0x1000000 | (fid & 0xF000) | (anon_alias[fid & 0xFFF] & 0xFFF); } } return -1; } // 0x419A78 int art_data_size(int fid, int* sizePtr) { int oldDb = -1; int result = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { oldDb = db_current(); db_select(critter_db_handle); } char* artFilePath = art_get_name(fid); if (artFilePath != NULL) { int fileSize; bool loaded = false; if (darn_foreigners) { char* pch = strchr(artFilePath, '\\'); if (pch == NULL) { pch = artFilePath; } char localizedPath[MAX_PATH]; sprintf(localizedPath, "art\\%s\\%s", darn_foreign_sub_path, pch); if (db_dir_entry(localizedPath, &fileSize) == 0) { loaded = true; } } if (!loaded) { if (db_dir_entry(artFilePath, &fileSize) == 0) { loaded = true; } } if (loaded) { *sizePtr = fileSize; result = 0; } } if (oldDb != -1) { db_select(oldDb); } return result; } // 0x419B78 int art_data_load(int fid, int* sizePtr, unsigned char* data) { int oldDb = -1; int result = -1; if (FID_TYPE(fid) == OBJ_TYPE_CRITTER) { oldDb = db_current(); db_select(critter_db_handle); } char* artFileName = art_get_name(fid); if (artFileName != NULL) { bool loaded = false; if (darn_foreigners) { char* pch = strchr(artFileName, '\\'); if (pch == NULL) { pch = artFileName; } char localizedPath[MAX_PATH]; sprintf(localizedPath, "art\\%s\\%s", darn_foreign_sub_path, pch); if (load_frame_into(localizedPath, data) == 0) { loaded = true; } } if (!loaded) { if (load_frame_into(artFileName, data) == 0) { loaded = true; } } if (loaded) { // TODO: Why it adds 74? *sizePtr = ((Art*)data)->field_3A + 74; result = 0; } } if (oldDb != -1) { db_select(oldDb); } return result; } // 0x419C80 void art_data_free(void* ptr) { mem_free(ptr); } // 0x419C88 int art_id(int objectType, int frmId, int animType, int a3, int rotation) { int v7, v8, v9, v10; v10 = rotation; if (objectType != OBJ_TYPE_CRITTER) { goto zero; } if (animType == ANIM_FIRE_DANCE || animType < ANIM_FALL_BACK || animType > ANIM_FALL_FRONT_BLOOD) { goto zero; } v7 = ((a3 << 12) & 0xF000) | ((animType << 16) & 0xFF0000) | 0x1000000; v8 = ((rotation << 28) & 0x70000000) | v7; v9 = frmId & 0xFFF; if (art_exists(v9 | v8) != 0) { goto out; } if (objectType == rotation) { goto zero; } v10 = objectType; if (art_exists(v9 | v7 | 0x10000000) != 0) { goto out; } zero: v10 = 0; out: return ((v10 << 28) & 0x70000000) | (objectType << 24) | ((animType << 16) & 0xFF0000) | ((a3 << 12) & 0xF000) | (frmId & 0xFFF); } ================================================ FILE: src/game/art.h ================================================ #ifndef FALLOUT_GAME_ART_H_ #define FALLOUT_GAME_ART_H_ #include "game/cache.h" #include "game/heap.h" #include "game/object_types.h" #include "game/proto_types.h" typedef enum Head { HEAD_INVALID, HEAD_MARCUS, HEAD_MYRON, HEAD_ELDER, HEAD_LYNETTE, HEAD_HAROLD, HEAD_TANDI, HEAD_COM_OFFICER, HEAD_SULIK, HEAD_PRESIDENT, HEAD_HAKUNIN, HEAD_BOSS, HEAD_DYING_HAKUNIN, HEAD_COUNT, } Head; typedef enum HeadAnimation { HEAD_ANIMATION_VERY_GOOD_REACTION = 0, FIDGET_GOOD = 1, HEAD_ANIMATION_GOOD_TO_NEUTRAL = 2, HEAD_ANIMATION_NEUTRAL_TO_GOOD = 3, FIDGET_NEUTRAL = 4, HEAD_ANIMATION_NEUTRAL_TO_BAD = 5, HEAD_ANIMATION_BAD_TO_NEUTRAL = 6, FIDGET_BAD = 7, HEAD_ANIMATION_VERY_BAD_REACTION = 8, HEAD_ANIMATION_GOOD_PHONEMES = 9, HEAD_ANIMATION_NEUTRAL_PHONEMES = 10, HEAD_ANIMATION_BAD_PHONEMES = 11, } HeadAnimation; typedef enum Background { BACKGROUND_0, BACKGROUND_1, BACKGROUND_2, BACKGROUND_HUB, BACKGROUND_NECROPOLIS, BACKGROUND_BROTHERHOOD, BACKGROUND_MILITARY_BASE, BACKGROUND_JUNK_TOWN, BACKGROUND_CATHEDRAL, BACKGROUND_SHADY_SANDS, BACKGROUND_VAULT, BACKGROUND_MASTER, BACKGROUND_FOLLOWER, BACKGROUND_RAIDERS, BACKGROUND_CAVE, BACKGROUND_ENCLAVE, BACKGROUND_WASTELAND, BACKGROUND_BOSS, BACKGROUND_PRESIDENT, BACKGROUND_TENT, BACKGROUND_ADOBE, BACKGROUND_COUNT, } Background; #pragma pack(2) typedef struct Art { int field_0; short framesPerSecond; short actionFrame; short frameCount; short xOffsets[6]; short yOffsets[6]; int dataOffsets[6]; int field_3A; } Art; #pragma pack() static_assert(sizeof(Art) == 62, "wrong size"); typedef struct ArtFrame { short width; short height; int size; short x; short y; } ArtFrame; typedef struct ArtListDescription { int flags; char name[16]; char* fileNames; // dynamic array of null terminated strings 13 bytes long each void* field_18; int fileNamesLength; // number of entries in list } ArtListDescription; typedef struct HeadDescription { int goodFidgetCount; int neutralFidgetCount; int badFidgetCount; } HeadDescription; typedef enum WeaponAnimation { WEAPON_ANIMATION_NONE, WEAPON_ANIMATION_KNIFE, // d WEAPON_ANIMATION_CLUB, // e WEAPON_ANIMATION_HAMMER, // f WEAPON_ANIMATION_SPEAR, // g WEAPON_ANIMATION_PISTOL, // h WEAPON_ANIMATION_SMG, // i WEAPON_ANIMATION_SHOTGUN, // j WEAPON_ANIMATION_LASER_RIFLE, // k WEAPON_ANIMATION_MINIGUN, // l WEAPON_ANIMATION_LAUNCHER, // m WEAPON_ANIMATION_COUNT, } WeaponAnimation; typedef enum DudeNativeLook { // Hero looks as one the tribals (before finishing Temple of Trails). DUDE_NATIVE_LOOK_TRIBAL, // Hero have finished Temple of Trails and received Vault Jumpsuit. DUDE_NATIVE_LOOK_JUMPSUIT, DUDE_NATIVE_LOOK_COUNT, } DudeNativeLook; extern int art_vault_guy_num; extern int art_vault_person_nums[DUDE_NATIVE_LOOK_COUNT][GENDER_COUNT]; extern int art_mapper_blank_tile; extern Cache art_cache; extern HeadDescription* head_info; extern int* anon_alias; extern int* artCritterFidShouldRunData; int art_init(); void art_reset(); void art_exit(); char* art_dir(int objectType); int art_get_disable(int objectType); void art_toggle_disable(int objectType); int art_total(int objectType); int art_head_fidgets(int headFid); void scale_art(int fid, unsigned char* dest, int width, int height, int pitch); Art* art_ptr_lock(int fid, CacheEntry** cache_entry); unsigned char* art_ptr_lock_data(int fid, int frame, int direction, CacheEntry** out_cache_entry); unsigned char* art_lock(int fid, CacheEntry** out_cache_entry, int* widthPtr, int* heightPtr); int art_ptr_unlock(CacheEntry* cache_entry); int art_discard(int fid); int art_flush(); int art_get_base_name(int objectType, int a2, char* a3); int art_get_code(int a1, int a2, char* a3, char* a4); char* art_get_name(int a1); int art_read_lst(const char* path, char** artListPtr, int* artListSizePtr); int art_frame_fps(Art* art); int art_frame_action_frame(Art* art); int art_frame_max_frame(Art* art); int art_frame_width(Art* art, int frame, int direction); int art_frame_length(Art* art, int frame, int direction); int art_frame_width_length(Art* art, int frame, int direction, int* out_width, int* out_height); int art_frame_hot(Art* art, int frame, int direction, int* a4, int* a5); int art_frame_offset(Art* art, int rotation, int* out_offset_x, int* out_offset_y); unsigned char* art_frame_data(Art* art, int frame, int direction); ArtFrame* frame_ptr(Art* art, int frame, int direction); bool art_exists(int fid); bool art_fid_valid(int fid); int art_alias_num(int a1); int artCritterFidShouldRun(int a1); int art_alias_fid(int fid); int art_data_size(int a1, int* out_size); int art_data_load(int a1, int* a2, unsigned char* data); void art_data_free(void* ptr); int art_id(int objectType, int frmId, int animType, int a4, int rotation); #endif /* FALLOUT_GAME_ART_H_ */ ================================================ FILE: src/game/artload.c ================================================ #include "game/artload.h" #include "plib/gnw/memory.h" static int art_readSubFrameData(unsigned char* data, File* stream, int count); static int art_readFrameData(Art* art, File* stream); // 0x419D60 static int art_readSubFrameData(unsigned char* data, File* stream, int count) { unsigned char* ptr = data; for (int index = 0; index < count; index++) { ArtFrame* frame = (ArtFrame*)ptr; if (db_freadShort(stream, &(frame->width)) == -1) return -1; if (db_freadShort(stream, &(frame->height)) == -1) return -1; if (db_freadInt(stream, &(frame->size)) == -1) return -1; if (db_freadShort(stream, &(frame->x)) == -1) return -1; if (db_freadShort(stream, &(frame->y)) == -1) return -1; if (db_fread(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1; ptr += sizeof(ArtFrame) + frame->size; } return 0; } // 0x419E1C static int art_readFrameData(Art* art, File* stream) { if (db_freadInt(stream, &(art->field_0)) == -1) return -1; if (db_freadShort(stream, &(art->framesPerSecond)) == -1) return -1; if (db_freadShort(stream, &(art->actionFrame)) == -1) return -1; if (db_freadShort(stream, &(art->frameCount)) == -1) return -1; if (db_freadShortCount(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1; if (db_freadShortCount(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1; if (db_freadIntCount(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1; if (db_freadInt(stream, &(art->field_3A)) == -1) return -1; return 0; } // NOTE: Unused. // // 0x419EC0 int load_frame(const char* path, Art** artPtr) { int size; File* stream; int index; if (db_dir_entry(path, &size) == -1) { return -2; } *artPtr = (Art*)mem_malloc(size); if (*artPtr == NULL) { return -1; } stream = db_fopen(path, "rb"); if (stream == NULL) { return -2; } if (art_readFrameData(*artPtr, stream) != 0) { db_fclose(stream); mem_free(*artPtr); return -3; } for (index = 0; index < ROTATION_COUNT; index++) { if (index == 0 || (*artPtr)->dataOffsets[index - 1] != (*artPtr)->dataOffsets[index]) { if (art_readSubFrameData((unsigned char*)(*artPtr) + sizeof(Art) + (*artPtr)->dataOffsets[index], stream, (*artPtr)->frameCount) != 0) { break; } } } if (index < ROTATION_COUNT) { db_fclose(stream); mem_free(*artPtr); return -5; } db_fclose(stream); return 0; } // 0x419FC0 int load_frame_into(const char* path, unsigned char* data) { File* stream = db_fopen(path, "rb"); if (stream == NULL) { return -2; } Art* art = (Art*)data; if (art_readFrameData(art, stream) != 0) { db_fclose(stream); return -3; } for (int index = 0; index < ROTATION_COUNT; index++) { if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) { if (art_readSubFrameData(data + sizeof(Art) + art->dataOffsets[index], stream, art->frameCount) != 0) { db_fclose(stream); return -5; } } } db_fclose(stream); return 0; } // NOTE: Unused. // // 0x41A070 int art_writeSubFrameData(unsigned char* data, File* stream, int count) { unsigned char* ptr = data; for (int index = 0; index < count; index++) { ArtFrame* frame = (ArtFrame*)ptr; if (db_fwriteShort(stream, frame->width) == -1) return -1; if (db_fwriteShort(stream, frame->height) == -1) return -1; if (db_fwriteInt(stream, frame->size) == -1) return -1; if (db_fwriteShort(stream, frame->x) == -1) return -1; if (db_fwriteShort(stream, frame->y) == -1) return -1; if (db_fwrite(ptr + sizeof(ArtFrame), frame->size, 1, stream) != 1) return -1; ptr += sizeof(ArtFrame) + frame->size; } return 0; } // NOTE: Unused. // // 0x41A138 int art_writeFrameData(Art* art, File* stream) { if (db_fwriteInt(stream, art->field_0) == -1) return -1; if (db_fwriteShort(stream, art->framesPerSecond) == -1) return -1; if (db_fwriteShort(stream, art->actionFrame) == -1) return -1; if (db_fwriteShort(stream, art->frameCount) == -1) return -1; if (db_fwriteShortCount(stream, art->xOffsets, ROTATION_COUNT) == -1) return -1; if (db_fwriteShortCount(stream, art->yOffsets, ROTATION_COUNT) == -1) return -1; if (db_fwriteIntCount(stream, art->dataOffsets, ROTATION_COUNT) == -1) return -1; if (db_fwriteInt(stream, art->field_3A) == -1) return -1; return 0; } // NOTE: Unused. // // 0x41A1E8 int save_frame(const char* path, unsigned char* data) { if (data == NULL) { return -1; } File* stream = db_fopen(path, "wb"); if (stream == NULL) { return -1; } Art* art = (Art*)data; if (art_writeFrameData(art, stream) == -1) { db_fclose(stream); return -1; } for (int index = 0; index < ROTATION_COUNT; index++) { if (index == 0 || art->dataOffsets[index - 1] != art->dataOffsets[index]) { if (art_writeSubFrameData(data + sizeof(Art) + art->dataOffsets[index], stream, art->frameCount) != 0) { db_fclose(stream); return -1; } } } db_fclose(stream); return 0; } ================================================ FILE: src/game/artload.h ================================================ #ifndef FALLOUT_GAME_ARTLOAD_H_ #define FALLOUT_GAME_ARTLOAD_H_ #include "game/art.h" #include "plib/db/db.h" int load_frame(const char* path, Art** artPtr); int load_frame_into(const char* path, unsigned char* data); int art_writeSubFrameData(unsigned char* data, File* stream, int count); int art_writeFrameData(Art* art, File* stream); int save_frame(const char* path, unsigned char* data); #endif /* FALLOUT_GAME_ARTLOAD_H_ */ ================================================ FILE: src/game/automap.c ================================================ #include "game/automap.h" #include #include #include "plib/color/color.h" #include "game/config.h" #include "plib/gnw/input.h" #include "game/bmpdlog.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gsound.h" #include "game/graphlib.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "plib/gnw/text.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #define AUTOMAP_OFFSET_COUNT (AUTOMAP_MAP_COUNT * ELEVATION_COUNT) #define AUTOMAP_WINDOW_X 75 #define AUTOMAP_WINDOW_Y 0 #define AUTOMAP_WINDOW_WIDTH 519 #define AUTOMAP_WINDOW_HEIGHT 480 #define AUTOMAP_PIPBOY_VIEW_X 238 #define AUTOMAP_PIPBOY_VIEW_Y 105 // View options for rendering automap for map window. These are stored in // [autoflags] and is saved in save game file. typedef enum AutomapFlags { // NOTE: This is a special flag to denote the map is activated in the game (as // opposed to the mapper). It's always on. Turning it off produces nice color // coded map with all objects and their types visible, however there is no way // you can do it within the game UI. AUTOMAP_IN_GAME = 0x01, // High details is on. AUTOMAP_WTH_HIGH_DETAILS = 0x02, // Scanner is active. AUTOMAP_WITH_SCANNER = 0x04, } AutomapFlags; typedef enum AutomapFrm { AUTOMAP_FRM_BACKGROUND, AUTOMAP_FRM_BUTTON_UP, AUTOMAP_FRM_BUTTON_DOWN, AUTOMAP_FRM_SWITCH_UP, AUTOMAP_FRM_SWITCH_DOWN, AUTOMAP_FRM_COUNT, } AutomapFrm; static void draw_top_down_map(int window, int elevation, unsigned char* backgroundData, int flags); static int WriteAM_Entry(File* stream); static int AM_ReadEntry(int map, int elevation); static int WriteAM_Header(File* stream); static int AM_ReadMainHeader(File* stream); static void decode_map_data(int elevation); static int am_pip_init(); static int copy_file_data(File* stream1, File* stream2, int length); // 0x41ADE0 static const int defam[AUTOMAP_MAP_COUNT][ELEVATION_COUNT] = { { -1, -1, -1 }, { -1, -1, -1 }, { -1, -1, -1 }, }; // 0x41B560 static const int displayMapList[AUTOMAP_MAP_COUNT] = { -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; // 0x5108C4 static int autoflags = 0; // 0x56CB18 static AutomapHeader amdbhead; // 0x56D2A0 static AutomapEntry amdbsubhead; // 0x56D2A8 static unsigned char* cmpbuf; // 0x56D2A8 static unsigned char* ambuf; // automap_init // 0x41B7F4 int automap_init() { autoflags = 0; am_pip_init(); return 0; } // 0x41B808 int automap_reset() { autoflags = 0; am_pip_init(); return 0; } // 0x41B81C void automap_exit() { char* masterPatchesPath; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) { char path[MAX_PATH]; sprintf(path, "%s\\%s\\%s", masterPatchesPath, "MAPS", AUTOMAP_DB); remove(path); } } // 0x41B87C int automap_load(File* stream) { return db_freadInt(stream, &autoflags); } // 0x41B898 int automap_save(File* stream) { return db_fwriteInt(stream, autoflags); } // 0x41B8B4 int automapDisplayMap(int map) { return displayMapList[map]; } // 0x41B8BC void automap(bool isInGame, bool isUsingScanner) { // 0x41B7E0 static const int frmIds[AUTOMAP_FRM_COUNT] = { 171, // automap.frm - automap window 8, // lilredup.frm - little red button up 9, // lilreddn.frm - little red button down 172, // autoup.frm - switch up 173, // autodwn.frm - switch down }; unsigned char* frmData[AUTOMAP_FRM_COUNT]; CacheEntry* frmHandle[AUTOMAP_FRM_COUNT]; for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, frmIds[index], 0, 0, 0); frmData[index] = art_ptr_lock_data(fid, 0, 0, &(frmHandle[index])); if (frmData[index] == NULL) { while (--index >= 0) { art_ptr_unlock(frmHandle[index]); } return; } } int color; if (isInGame) { color = colorTable[8456]; obj_process_seen(); } else { color = colorTable[22025]; } int oldFont = text_curr(); text_font(101); int automapWindowX = AUTOMAP_WINDOW_X; int automapWindowY = AUTOMAP_WINDOW_Y; int window = win_add(automapWindowX, automapWindowY, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); int scannerBtn = win_register_button(window, 111, 454, 15, 16, -1, -1, -1, KEY_LOWERCASE_S, frmData[AUTOMAP_FRM_BUTTON_UP], frmData[AUTOMAP_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (scannerBtn != -1) { win_register_button_sound_func(scannerBtn, gsound_red_butt_press, gsound_red_butt_release); } int cancelBtn = win_register_button(window, 277, 454, 15, 16, -1, -1, -1, KEY_ESCAPE, frmData[AUTOMAP_FRM_BUTTON_UP], frmData[AUTOMAP_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (cancelBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } int switchBtn = win_register_button(window, 457, 340, 42, 74, -1, -1, KEY_LOWERCASE_L, KEY_LOWERCASE_H, frmData[AUTOMAP_FRM_SWITCH_UP], frmData[AUTOMAP_FRM_SWITCH_DOWN], NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01); if (switchBtn != -1) { win_register_button_sound_func(switchBtn, gsound_toggle_butt_press, gsound_toggle_butt_release); } if ((autoflags & AUTOMAP_WTH_HIGH_DETAILS) == 0) { win_set_button_rest_state(switchBtn, 1, 0); } int elevation = map_elevation; autoflags &= AUTOMAP_WTH_HIGH_DETAILS; if (isInGame) { autoflags |= AUTOMAP_IN_GAME; } if (isUsingScanner) { autoflags |= AUTOMAP_WITH_SCANNER; } draw_top_down_map(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], autoflags); bool isoWasEnabled = map_disable_bk_processes(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); bool done = false; while (!done) { bool needsRefresh = false; // FIXME: There is minor bug in the interface - pressing H/L to toggle // high/low details does not update switch state. int keyCode = get_input(); switch (keyCode) { case KEY_TAB: case KEY_ESCAPE: case KEY_UPPERCASE_A: case KEY_LOWERCASE_A: done = true; break; case KEY_UPPERCASE_H: case KEY_LOWERCASE_H: if ((autoflags & AUTOMAP_WTH_HIGH_DETAILS) == 0) { autoflags |= AUTOMAP_WTH_HIGH_DETAILS; needsRefresh = true; } break; case KEY_UPPERCASE_L: case KEY_LOWERCASE_L: if ((autoflags & AUTOMAP_WTH_HIGH_DETAILS) != 0) { autoflags &= ~AUTOMAP_WTH_HIGH_DETAILS; needsRefresh = true; } break; case KEY_UPPERCASE_S: case KEY_LOWERCASE_S: if (elevation != map_elevation) { elevation = map_elevation; needsRefresh = true; } if ((autoflags & AUTOMAP_WITH_SCANNER) == 0) { Object* scanner = NULL; Object* item1 = inven_left_hand(obj_dude); if (item1 != NULL && item1->pid == PROTO_ID_MOTION_SENSOR) { scanner = item1; } else { Object* item2 = inven_right_hand(obj_dude); if (item2 != NULL && item2->pid == PROTO_ID_MOTION_SENSOR) { scanner = item2; } } if (scanner != NULL && item_m_curr_charges(scanner) > 0) { needsRefresh = true; autoflags |= AUTOMAP_WITH_SCANNER; item_m_dec_charges(scanner); } else { gsound_play_sfx_file("iisxxxx1"); MessageListItem messageListItem; // 17 - The motion sensor is not installed. // 18 - The motion sensor has no charges remaining. const char* title = getmsg(&misc_message_file, &messageListItem, scanner != NULL ? 18 : 17); dialog_out(title, NULL, 0, 165, 140, colorTable[32328], NULL, colorTable[32328], 0); } } break; case KEY_CTRL_Q: case KEY_ALT_X: case KEY_F10: game_quit_with_confirm(); break; case KEY_F12: dump_screen(); break; } if (game_user_wants_to_quit != 0) { break; } if (needsRefresh) { draw_top_down_map(window, elevation, frmData[AUTOMAP_FRM_BACKGROUND], autoflags); needsRefresh = false; } } if (isoWasEnabled) { map_enable_bk_processes(); } win_delete(window); text_font(oldFont); for (int index = 0; index < AUTOMAP_FRM_COUNT; index++) { art_ptr_unlock(frmHandle[index]); } } // Renders automap in Map window. // // 0x41BD1C static void draw_top_down_map(int window, int elevation, unsigned char* backgroundData, int flags) { int color; if ((flags & AUTOMAP_IN_GAME) != 0) { color = colorTable[8456]; } else { color = colorTable[22025]; } win_fill(window, 0, 0, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, color); win_border(window); unsigned char* windowBuffer = win_get_buf(window); buf_to_buf(backgroundData, AUTOMAP_WINDOW_WIDTH, AUTOMAP_WINDOW_HEIGHT, AUTOMAP_WINDOW_WIDTH, windowBuffer, AUTOMAP_WINDOW_WIDTH); for (Object* object = obj_find_first_at(elevation); object != NULL; object = obj_find_next_at()) { if (object->tile == -1) { continue; } int objectType = FID_TYPE(object->fid); unsigned char objectColor; if ((flags & AUTOMAP_IN_GAME) != 0) { if (objectType == OBJ_TYPE_CRITTER && (object->flags & OBJECT_HIDDEN) == 0 && (flags & AUTOMAP_WITH_SCANNER) != 0 && (object->data.critter.combat.results & DAM_DEAD) == 0) { objectColor = colorTable[31744]; } else { if ((object->flags & OBJECT_SEEN) == 0) { continue; } if (object->pid == PROTO_ID_0x2000031) { objectColor = colorTable[32328]; } else if (objectType == OBJ_TYPE_WALL) { objectColor = colorTable[992]; } else if (objectType == OBJ_TYPE_SCENERY && (flags & AUTOMAP_WTH_HIGH_DETAILS) != 0 && object->pid != PROTO_ID_0x2000158) { objectColor = colorTable[480]; } else if (object == obj_dude) { objectColor = colorTable[31744]; } else { objectColor = colorTable[0]; } } } int v10 = -2 * (object->tile % 200) - 10 + AUTOMAP_WINDOW_WIDTH * (2 * (object->tile / 200) + 9) - 60; if ((flags & AUTOMAP_IN_GAME) == 0) { switch (objectType) { case OBJ_TYPE_ITEM: objectColor = colorTable[6513]; break; case OBJ_TYPE_CRITTER: objectColor = colorTable[28672]; break; case OBJ_TYPE_SCENERY: objectColor = colorTable[448]; break; case OBJ_TYPE_WALL: objectColor = colorTable[12546]; break; case OBJ_TYPE_MISC: objectColor = colorTable[31650]; break; default: objectColor = colorTable[0]; } } if (objectColor != colorTable[0]) { unsigned char* v12 = windowBuffer + v10; if ((flags & AUTOMAP_IN_GAME) != 0) { if (*v12 != colorTable[992] || objectColor != colorTable[480]) { v12[0] = objectColor; v12[1] = objectColor; } if (object == obj_dude) { v12[-1] = objectColor; v12[-AUTOMAP_WINDOW_WIDTH] = objectColor; v12[AUTOMAP_WINDOW_WIDTH] = objectColor; } } else { v12[0] = objectColor; v12[1] = objectColor; v12[AUTOMAP_WINDOW_WIDTH] = objectColor; v12[AUTOMAP_WINDOW_WIDTH + 1] = objectColor; v12[AUTOMAP_WINDOW_WIDTH - 1] = objectColor; v12[AUTOMAP_WINDOW_WIDTH + 2] = objectColor; v12[AUTOMAP_WINDOW_WIDTH * 2] = objectColor; v12[AUTOMAP_WINDOW_WIDTH * 2 + 1] = objectColor; } } } int textColor; if ((flags & AUTOMAP_IN_GAME) != 0) { textColor = colorTable[992]; } else { textColor = colorTable[12546]; } if (map_get_index_number() != -1) { char* areaName = map_get_short_name(map_get_index_number()); win_print(window, areaName, 240, 150, 380, textColor | 0x2000000); char* mapName = map_get_elev_idx(map_get_index_number(), elevation); win_print(window, mapName, 240, 150, 396, textColor | 0x2000000); } win_draw(window); } // Renders automap in Pipboy window. // // 0x41C004 int draw_top_down_map_pipboy(int window, int map, int elevation) { unsigned char* windowBuffer = win_get_buf(window) + 640 * AUTOMAP_PIPBOY_VIEW_Y + AUTOMAP_PIPBOY_VIEW_X; unsigned char wallColor = colorTable[992]; unsigned char sceneryColor = colorTable[480]; ambuf = (unsigned char*)mem_malloc(11024); if (ambuf == NULL) { debug_printf("\nAUTOMAP: Error allocating data buffer!\n"); return -1; } if (AM_ReadEntry(map, elevation) == -1) { mem_free(ambuf); return -1; } int v1 = 0; unsigned char v2 = 0; unsigned char* ptr = ambuf; // FIXME: This loop is implemented incorrectly. Automap requires 400x400 px, // but it's top offset is 105, which gives max y 505. It only works because // lower portions of automap data contains zeroes. If it doesn't this loop // will try to set pixels outside of window buffer, which usually leads to // crash. for (int y = 0; y < HEX_GRID_HEIGHT; y++) { for (int x = 0; x < HEX_GRID_WIDTH; x++) { v1 -= 1; if (v1 <= 0) { v1 = 4; v2 = *ptr++; } switch ((v2 & 0xC0) >> 6) { case 1: *windowBuffer++ = wallColor; *windowBuffer++ = wallColor; break; case 2: *windowBuffer++ = sceneryColor; *windowBuffer++ = sceneryColor; break; default: windowBuffer += 2; break; } v2 <<= 2; } windowBuffer += 640 + 240; } mem_free(ambuf); return 0; } // automap_pip_save // 0x41C0F0 int automap_pip_save() { int map = map_get_index_number(); int elevation = map_elevation; int entryOffset = amdbhead.offsets[map][elevation]; if (entryOffset < 0) { return 0; } debug_printf("\nAUTOMAP: Saving AutoMap DB index %d, level %d\n", map, elevation); bool dataBuffersAllocated = false; ambuf = (unsigned char*)mem_malloc(11024); if (ambuf != NULL) { cmpbuf = (unsigned char*)mem_malloc(11024); if (cmpbuf != NULL) { dataBuffersAllocated = true; } } if (!dataBuffersAllocated) { // FIXME: Leaking ambuf. debug_printf("\nAUTOMAP: Error allocating data buffers!\n"); return -1; } // NOTE: Not sure about the size. char path[256]; sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); File* stream1 = db_fopen(path, "r+b"); if (stream1 == NULL) { debug_printf("\nAUTOMAP: Error opening automap database file!\n"); debug_printf("Error continued: automap_pip_save: path: %s", path); mem_free(ambuf); mem_free(cmpbuf); return -1; } if (AM_ReadMainHeader(stream1) == -1) { debug_printf("\nAUTOMAP: Error reading automap database file header!\n"); mem_free(ambuf); mem_free(cmpbuf); db_fclose(stream1); return -1; } decode_map_data(elevation); int compressedDataSize = CompLZS(ambuf, cmpbuf, 10000); if (compressedDataSize == -1) { amdbsubhead.dataSize = 10000; amdbsubhead.isCompressed = 0; } else { amdbsubhead.dataSize = compressedDataSize; amdbsubhead.isCompressed = 1; } if (entryOffset != 0) { sprintf(path, "%s\\%s", "MAPS", AUTOMAP_TMP); File* stream2 = db_fopen(path, "wb"); if (stream2 == NULL) { debug_printf("\nAUTOMAP: Error creating temp file!\n"); mem_free(ambuf); mem_free(cmpbuf); db_fclose(stream1); return -1; } db_rewind(stream1); if (copy_file_data(stream1, stream2, entryOffset) == -1) { debug_printf("\nAUTOMAP: Error copying file data!\n"); db_fclose(stream1); db_fclose(stream2); mem_free(ambuf); mem_free(cmpbuf); return -1; } if (WriteAM_Entry(stream2) == -1) { db_fclose(stream1); mem_free(ambuf); mem_free(cmpbuf); return -1; } int nextEntryDataSize; if (db_freadLong(stream1, &nextEntryDataSize) == -1) { debug_printf("\nAUTOMAP: Error reading database #1!\n"); db_fclose(stream1); db_fclose(stream2); mem_free(ambuf); mem_free(cmpbuf); return -1; } int automapDataSize = db_filelength(stream1); if (automapDataSize == -1) { debug_printf("\nAUTOMAP: Error reading database #2!\n"); db_fclose(stream1); db_fclose(stream2); mem_free(ambuf); mem_free(cmpbuf); return -1; } int nextEntryOffset = entryOffset + nextEntryDataSize + 5; if (automapDataSize != nextEntryOffset) { if (db_fseek(stream1, nextEntryOffset, SEEK_SET) == -1) { debug_printf("\nAUTOMAP: Error writing temp data!\n"); db_fclose(stream1); db_fclose(stream2); mem_free(ambuf); mem_free(cmpbuf); return -1; } if (copy_file_data(stream1, stream2, automapDataSize - nextEntryOffset) == -1) { debug_printf("\nAUTOMAP: Error copying file data!\n"); db_fclose(stream1); db_fclose(stream2); mem_free(ambuf); mem_free(cmpbuf); return -1; } } int diff = amdbsubhead.dataSize - nextEntryDataSize; for (int map = 0; map < AUTOMAP_MAP_COUNT; map++) { for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { if (amdbhead.offsets[map][elevation] > entryOffset) { amdbhead.offsets[map][elevation] += diff; } } } amdbhead.dataSize += diff; if (WriteAM_Header(stream2) == -1) { db_fclose(stream1); mem_free(ambuf); mem_free(cmpbuf); return -1; } db_fseek(stream2, 0, SEEK_END); db_fclose(stream2); db_fclose(stream1); mem_free(ambuf); mem_free(cmpbuf); char* masterPatchesPath; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) { debug_printf("\nAUTOMAP: Error reading config info!\n"); return -1; } // NOTE: Not sure about the size. char automapDbPath[512]; sprintf(automapDbPath, "%s\\%s\\%s", masterPatchesPath, "MAPS", AUTOMAP_DB); if (remove(automapDbPath) != 0) { debug_printf("\nAUTOMAP: Error removing database!\n"); return -1; } // NOTE: Not sure about the size. char automapTmpPath[512]; sprintf(automapTmpPath, "%s\\%s\\%s", masterPatchesPath, "MAPS", AUTOMAP_TMP); if (rename(automapTmpPath, automapDbPath) != 0) { debug_printf("\nAUTOMAP: Error renaming database!\n"); return -1; } } else { bool proceed = true; if (db_fseek(stream1, 0, SEEK_END) != -1) { if (db_ftell(stream1) != amdbhead.dataSize) { proceed = false; } } else { proceed = false; } if (!proceed) { debug_printf("\nAUTOMAP: Error reading automap database file header!\n"); mem_free(ambuf); mem_free(cmpbuf); db_fclose(stream1); return -1; } if (WriteAM_Entry(stream1) == -1) { mem_free(ambuf); mem_free(cmpbuf); return -1; } amdbhead.offsets[map][elevation] = amdbhead.dataSize; amdbhead.dataSize += amdbsubhead.dataSize + 5; if (WriteAM_Header(stream1) == -1) { mem_free(ambuf); mem_free(cmpbuf); return -1; } db_fseek(stream1, 0, SEEK_END); db_fclose(stream1); mem_free(ambuf); mem_free(cmpbuf); } return 1; } // Saves automap entry into stream. // // 0x41C844 static int WriteAM_Entry(File* stream) { unsigned char* buffer; if (amdbsubhead.isCompressed == 1) { buffer = cmpbuf; } else { buffer = ambuf; } if (db_fwriteLong(stream, amdbsubhead.dataSize) == -1) { goto err; } if (db_fwriteByte(stream, amdbsubhead.isCompressed) == -1) { goto err; } if (db_fwriteByteCount(stream, buffer, amdbsubhead.dataSize) == -1) { goto err; } return 0; err: debug_printf("\nAUTOMAP: Error writing automap database entry data!\n"); db_fclose(stream); return -1; } // 0x41C8CC static int AM_ReadEntry(int map, int elevation) { cmpbuf = NULL; char path[MAX_PATH]; sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); bool success = true; File* stream = db_fopen(path, "r+b"); if (stream == NULL) { debug_printf("\nAUTOMAP: Error opening automap database file!\n"); debug_printf("Error continued: AM_ReadEntry: path: %s", path); return -1; } if (AM_ReadMainHeader(stream) == -1) { debug_printf("\nAUTOMAP: Error reading automap database header!\n"); db_fclose(stream); return -1; } if (amdbhead.offsets[map][elevation] <= 0) { success = false; goto out; } if (db_fseek(stream, amdbhead.offsets[map][elevation], SEEK_SET) == -1) { success = false; goto out; } if (db_freadLong(stream, &(amdbsubhead.dataSize)) == -1) { success = false; goto out; } if (db_freadByte(stream, &(amdbsubhead.isCompressed)) == -1) { success = false; goto out; } if (amdbsubhead.isCompressed == 1) { cmpbuf = (unsigned char*)mem_malloc(11024); if (cmpbuf == NULL) { debug_printf("\nAUTOMAP: Error allocating decompression buffer!\n"); db_fclose(stream); return -1; } if (db_freadByteCount(stream, cmpbuf, amdbsubhead.dataSize) == -1) { success = 0; goto out; } if (DecodeLZS(cmpbuf, ambuf, 10000) == -1) { debug_printf("\nAUTOMAP: Error decompressing DB entry!\n"); db_fclose(stream); return -1; } } else { if (db_freadByteCount(stream, ambuf, amdbsubhead.dataSize) == -1) { success = false; goto out; } } out: db_fclose(stream); if (!success) { debug_printf("\nAUTOMAP: Error reading automap database entry data!\n"); return -1; } if (cmpbuf != NULL) { mem_free(cmpbuf); } return 0; } // Saves automap.db header. // // 0x41CAD8 static int WriteAM_Header(File* stream) { db_rewind(stream); if (db_fwriteByte(stream, amdbhead.version) == -1) { goto err; } if (db_fwriteLong(stream, amdbhead.dataSize) == -1) { goto err; } if (db_fwriteLongCount(stream, (int*)amdbhead.offsets, AUTOMAP_OFFSET_COUNT) == -1) { goto err; } return 0; err: debug_printf("\nAUTOMAP: Error writing automap database header!\n"); db_fclose(stream); return -1; } // Loads automap.db header. // // 0x41CB50 static int AM_ReadMainHeader(File* stream) { if (db_freadByte(stream, &(amdbhead.version)) == -1) { return -1; } if (db_freadLong(stream, &(amdbhead.dataSize)) == -1) { return -1; } if (db_freadLongCount(stream, (int*)amdbhead.offsets, AUTOMAP_OFFSET_COUNT) == -1) { return -1; } if (amdbhead.version != 1) { return -1; } return 0; } // 0x41CBA4 static void decode_map_data(int elevation) { memset(ambuf, 0, SQUARE_GRID_SIZE); obj_process_seen(); Object* object = obj_find_first_at(elevation); while (object != NULL) { if (object->tile != -1 && (object->flags & OBJECT_SEEN) != 0) { int contentType; int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_SCENERY && object->pid != PROTO_ID_0x2000158) { contentType = 2; } else if (objectType == OBJ_TYPE_WALL) { contentType = 1; } else { contentType = 0; } if (contentType != 0) { int v1 = 200 - object->tile % 200; int v2 = v1 / 4 + 50 * (object->tile / 200); int v3 = 2 * (3 - v1 % 4); ambuf[v2] &= ~(0x03 << v3); ambuf[v2] |= (contentType << v3); } } object = obj_find_next_at(); } } // 0x41CC98 static int am_pip_init() { amdbhead.version = 1; amdbhead.dataSize = 1925; memcpy(amdbhead.offsets, defam, sizeof(defam)); char path[MAX_PATH]; sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); File* stream = db_fopen(path, "wb"); if (stream == NULL) { debug_printf("\nAUTOMAP: Error creating automap database file!\n"); return -1; } if (WriteAM_Header(stream) == -1) { return -1; } db_fclose(stream); return 0; } // NOTE: Unused. // // 0x41CD34 int YesWriteIndex(int mapIndex, int elevation) { if (mapIndex < AUTOMAP_MAP_COUNT && elevation < ELEVATION_COUNT && mapIndex >= 0 && elevation >= 0) { return defam[mapIndex][elevation] >= 0; } return 0; } // Copy data from stream1 to stream2. // // 0x41CD6C static int copy_file_data(File* stream1, File* stream2, int length) { void* buffer = mem_malloc(0xFFFF); if (buffer == NULL) { return -1; } // NOTE: Original code is slightly different, but does the same thing. while (length != 0) { int chunkLength = min(length, 0xFFFF); if (db_fread(buffer, chunkLength, 1, stream1) != 1) { break; } if (db_fwrite(buffer, chunkLength, 1, stream2) != 1) { break; } length -= chunkLength; } mem_free(buffer); if (length != 0) { return -1; } return 0; } // 0x41CE74 int ReadAMList(AutomapHeader** automapHeaderPtr) { char path[MAX_PATH]; sprintf(path, "%s\\%s", "MAPS", AUTOMAP_DB); File* stream = db_fopen(path, "rb"); if (stream == NULL) { debug_printf("\nAUTOMAP: Error opening database file for reading!\n"); debug_printf("Error continued: ReadAMList: path: %s", path); return -1; } if (AM_ReadMainHeader(stream) == -1) { debug_printf("\nAUTOMAP: Error reading automap database header pt2!\n"); db_fclose(stream); return -1; } db_fclose(stream); *automapHeaderPtr = &amdbhead; return 0; } ================================================ FILE: src/game/automap.h ================================================ #ifndef FALLOUT_GAME_AUTOMAP_H_ #define FALLOUT_GAME_AUTOMAP_H_ #include #include "plib/db/db.h" #include "game/map_defs.h" #define AUTOMAP_DB "AUTOMAP.DB" #define AUTOMAP_TMP "AUTOMAP.TMP" // The number of map entries that is stored in automap.db. // // NOTE: I don't know why this value is not equal to the number of maps. #define AUTOMAP_MAP_COUNT 160 typedef struct AutomapHeader { unsigned char version; // The size of entire automap database (including header itself). int dataSize; // Offsets from the beginning of the automap database file into // entries data. // // These offsets are specified for every map/elevation combination. A value // of 0 specifies that there is no data for appropriate map/elevation // combination. int offsets[AUTOMAP_MAP_COUNT][ELEVATION_COUNT]; } AutomapHeader; typedef struct AutomapEntry { int dataSize; unsigned char isCompressed; } AutomapEntry; int automap_init(); int automap_reset(); void automap_exit(); int automap_load(File* stream); int automap_save(File* stream); int automapDisplayMap(int map); void automap(bool isInGame, bool isUsingScanner); int draw_top_down_map_pipboy(int win, int map, int elevation); int automap_pip_save(); int YesWriteIndex(int mapIndex, int elevation); int ReadAMList(AutomapHeader** automapHeaderPtr); #endif /* FALLOUT_GAME_AUTOMAP_H_ */ ================================================ FILE: src/game/bmpdlog.c ================================================ #include "game/bmpdlog.h" #include #include #include "game/art.h" #include "game/editor.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gsound.h" #include "game/message.h" #include "plib/gnw/text.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #include "game/wordwrap.h" #define FILE_DIALOG_LINE_COUNT 12 #define FILE_DIALOG_DOUBLE_CLICK_DELAY 32 #define LOAD_FILE_DIALOG_DONE_BUTTON_X 58 #define LOAD_FILE_DIALOG_DONE_BUTTON_Y 187 #define LOAD_FILE_DIALOG_DONE_LABEL_X 79 #define LOAD_FILE_DIALOG_DONE_LABEL_Y 187 #define LOAD_FILE_DIALOG_CANCEL_BUTTON_X 163 #define LOAD_FILE_DIALOG_CANCEL_BUTTON_Y 187 #define LOAD_FILE_DIALOG_CANCEL_LABEL_X 182 #define LOAD_FILE_DIALOG_CANCEL_LABEL_Y 187 #define SAVE_FILE_DIALOG_DONE_BUTTON_X 58 #define SAVE_FILE_DIALOG_DONE_BUTTON_Y 214 #define SAVE_FILE_DIALOG_DONE_LABEL_X 79 #define SAVE_FILE_DIALOG_DONE_LABEL_Y 213 #define SAVE_FILE_DIALOG_CANCEL_BUTTON_X 163 #define SAVE_FILE_DIALOG_CANCEL_BUTTON_Y 214 #define SAVE_FILE_DIALOG_CANCEL_LABEL_X 182 #define SAVE_FILE_DIALOG_CANCEL_LABEL_Y 213 #define FILE_DIALOG_TITLE_X 49 #define FILE_DIALOG_TITLE_Y 16 #define FILE_DIALOG_SCROLL_BUTTON_X 36 #define FILE_DIALOG_SCROLL_BUTTON_Y 44 #define FILE_DIALOG_FILE_LIST_X 55 #define FILE_DIALOG_FILE_LIST_Y 49 #define FILE_DIALOG_FILE_LIST_WIDTH 190 #define FILE_DIALOG_FILE_LIST_HEIGHT 124 static void PrntFlist(unsigned char* buffer, char** fileList, int pageOffset, int fileListLength, int selectedIndex, int pitch); // 0x5108C8 int dbox[DIALOG_TYPE_COUNT] = { 218, // MEDIALOG.FRM - Medium generic dialog box 217, // LGDIALOG.FRM - Large generic dialog box }; // 0x5108D0 int ytable[DIALOG_TYPE_COUNT] = { 23, 27, }; // 0x5108D8 int xtable[DIALOG_TYPE_COUNT] = { 29, 29, }; // 0x5108E0 int doneY[DIALOG_TYPE_COUNT] = { 81, 98, }; // 0x5108E8 int doneX[DIALOG_TYPE_COUNT] = { 51, 37, }; // 0x5108F0 int dblines[DIALOG_TYPE_COUNT] = { 5, 6, }; // 0x510900 int flgids[FILE_DIALOG_FRM_COUNT] = { 224, // loadbox.frm - character editor 8, // lilredup.frm - little red button up 9, // lilreddn.frm - little red button down 181, // dnarwoff.frm - character editor 182, // dnarwon.frm - character editor 199, // uparwoff.frm - character editor 200, // uparwon.frm - character editor }; // 0x51091C int flgids2[FILE_DIALOG_FRM_COUNT] = { 225, // savebox.frm - character editor 8, // lilredup.frm - little red button up 9, // lilreddn.frm - little red button down 181, // dnarwoff.frm - character editor 182, // dnarwon.frm - character editor 199, // uparwoff.frm - character editor 200, // uparwon.frm - character editor }; // 0x41CF20 int dialog_out(const char* title, const char** body, int bodyLength, int x, int y, int titleColor, const char* a8, int bodyColor, int flags) { MessageList messageList; MessageListItem messageListItem; int savedFont = text_curr(); bool v86 = false; bool hasTwoButtons = false; if (a8 != NULL) { hasTwoButtons = true; } bool hasTitle = false; if (title != NULL) { hasTitle = true; } if ((flags & DIALOG_BOX_YES_NO) != 0) { hasTwoButtons = true; flags |= DIALOG_BOX_LARGE; flags &= ~DIALOG_BOX_0x20; } int maximumLineWidth = 0; if (hasTitle) { maximumLineWidth = text_width(title); } int linesCount = 0; for (int index = 0; index < bodyLength; index++) { // NOTE: Calls [text_width] twice because of [max] macro. maximumLineWidth = max(text_width(body[index]), maximumLineWidth); linesCount++; } int dialogType; if ((flags & DIALOG_BOX_LARGE) != 0 || hasTwoButtons) { dialogType = DIALOG_TYPE_LARGE; } else if ((flags & DIALOG_BOX_MEDIUM) != 0) { dialogType = DIALOG_TYPE_MEDIUM; } else { if (hasTitle) { linesCount++; } dialogType = maximumLineWidth > 168 || linesCount > 5 ? DIALOG_TYPE_LARGE : DIALOG_TYPE_MEDIUM; } CacheEntry* backgroundHandle; int backgroundWidth; int backgroundHeight; int fid = art_id(OBJ_TYPE_INTERFACE, dbox[dialogType], 0, 0, 0); unsigned char* background = art_lock(fid, &backgroundHandle, &backgroundWidth, &backgroundHeight); if (background == NULL) { text_font(savedFont); return -1; } int win = win_add(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { art_ptr_unlock(backgroundHandle); text_font(savedFont); return -1; } unsigned char* windowBuf = win_get_buf(win); memcpy(windowBuf, background, backgroundWidth * backgroundHeight); CacheEntry* doneBoxHandle = NULL; unsigned char* doneBox = NULL; int doneBoxWidth; int doneBoxHeight; CacheEntry* downButtonHandle = NULL; unsigned char* downButton = NULL; int downButtonWidth; int downButtonHeight; CacheEntry* upButtonHandle = NULL; unsigned char* upButton = NULL; if ((flags & DIALOG_BOX_0x20) == 0) { int doneBoxFid = art_id(OBJ_TYPE_INTERFACE, 209, 0, 0, 0); doneBox = art_lock(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight); if (doneBox == NULL) { art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } int downButtonFid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); downButton = art_lock(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight); if (downButton == NULL) { art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } int upButtonFid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); upButton = art_ptr_lock_data(upButtonFid, 0, 0, &upButtonHandle); if (upButton == NULL) { art_ptr_unlock(downButtonHandle); art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } int v27 = hasTwoButtons ? doneX[dialogType] : (backgroundWidth - doneBoxWidth) / 2; buf_to_buf(doneBox, doneBoxWidth, doneBoxHeight, doneBoxWidth, windowBuf + backgroundWidth * doneY[dialogType] + v27, backgroundWidth); if (!message_init(&messageList)) { art_ptr_unlock(upButtonHandle); art_ptr_unlock(downButtonHandle); art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "DBOX.MSG"); if (!message_load(&messageList, path)) { art_ptr_unlock(upButtonHandle); art_ptr_unlock(downButtonHandle); art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); // FIXME: Window is not removed. return -1; } text_font(103); // 100 - DONE // 101 - YES messageListItem.num = (flags & DIALOG_BOX_YES_NO) == 0 ? 100 : 101; if (message_search(&messageList, &messageListItem)) { text_to_buf(windowBuf + backgroundWidth * (doneY[dialogType] + 3) + v27 + 35, messageListItem.text, backgroundWidth, backgroundWidth, colorTable[18979]); } int btn = win_register_button(win, v27 + 13, doneY[dialogType] + 4, downButtonWidth, downButtonHeight, -1, -1, -1, 500, upButton, downButton, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } v86 = true; } if (hasTwoButtons && dialogType == DIALOG_TYPE_LARGE) { if (v86) { if ((flags & DIALOG_BOX_YES_NO) != 0) { a8 = getmsg(&messageList, &messageListItem, 102); } text_font(103); trans_buf_to_buf(doneBox, doneBoxWidth, doneBoxHeight, doneBoxWidth, windowBuf + backgroundWidth * doneY[dialogType] + doneX[dialogType] + doneBoxWidth + 24, backgroundWidth); text_to_buf(windowBuf + backgroundWidth * (doneY[dialogType] + 3) + doneX[dialogType] + doneBoxWidth + 59, a8, backgroundWidth, backgroundWidth, colorTable[18979]); int btn = win_register_button(win, doneBoxWidth + doneX[dialogType] + 37, doneY[dialogType] + 4, downButtonWidth, downButtonHeight, -1, -1, -1, 501, upButton, downButton, 0, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } else { int doneBoxFid = art_id(OBJ_TYPE_INTERFACE, 209, 0, 0, 0); unsigned char* doneBox = art_lock(doneBoxFid, &doneBoxHandle, &doneBoxWidth, &doneBoxHeight); if (doneBox == NULL) { art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } int downButtonFid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); unsigned char* downButton = art_lock(downButtonFid, &downButtonHandle, &downButtonWidth, &downButtonHeight); if (downButton == NULL) { art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } int upButtonFid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); unsigned char* upButton = art_ptr_lock_data(upButtonFid, 0, 0, &upButtonHandle); if (upButton == NULL) { art_ptr_unlock(downButtonHandle); art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } if (!message_init(&messageList)) { art_ptr_unlock(upButtonHandle); art_ptr_unlock(downButtonHandle); art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "DBOX.MSG"); if (!message_load(&messageList, path)) { art_ptr_unlock(upButtonHandle); art_ptr_unlock(downButtonHandle); art_ptr_unlock(doneBoxHandle); art_ptr_unlock(backgroundHandle); text_font(savedFont); win_delete(win); return -1; } trans_buf_to_buf(doneBox, doneBoxWidth, doneBoxHeight, doneBoxWidth, windowBuf + backgroundWidth * doneY[dialogType] + doneX[dialogType], backgroundWidth); text_font(103); text_to_buf(windowBuf + backgroundWidth * (doneY[dialogType] + 3) + doneX[dialogType] + 35, a8, backgroundWidth, backgroundWidth, colorTable[18979]); int btn = win_register_button(win, doneX[dialogType] + 13, doneY[dialogType] + 4, downButtonWidth, downButtonHeight, -1, -1, -1, 501, upButton, downButton, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } v86 = true; } } text_font(101); int v23 = ytable[dialogType]; if ((flags & DIALOG_BOX_NO_VERTICAL_CENTERING) == 0) { int v41 = dblines[dialogType] * text_height() / 2 + v23; v23 = v41 - ((bodyLength + 1) * text_height() / 2); } if (hasTitle) { if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) { text_to_buf(windowBuf + backgroundWidth * v23 + xtable[dialogType], title, backgroundWidth, backgroundWidth, titleColor); } else { int length = text_width(title); text_to_buf(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, title, backgroundWidth, backgroundWidth, titleColor); } v23 += text_height(); } for (int v94 = 0; v94 < bodyLength; v94++) { int len = text_width(body[v94]); if (len <= backgroundWidth - 26) { if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) { text_to_buf(windowBuf + backgroundWidth * v23 + xtable[dialogType], body[v94], backgroundWidth, backgroundWidth, bodyColor); } else { int length = text_width(body[v94]); text_to_buf(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, body[v94], backgroundWidth, backgroundWidth, bodyColor); } v23 += text_height(); } else { short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (word_wrap(body[v94], backgroundWidth - 26, beginnings, &count) != 0) { debug_printf("\nError: dialog_out"); } for (int v48 = 1; v48 < count; v48++) { int v51 = beginnings[v48] - beginnings[v48 - 1]; if (v51 >= 260) { v51 = 259; } char string[260]; strncpy(string, body[v94] + beginnings[v48 - 1], v51); string[v51] = '\0'; if ((flags & DIALOG_BOX_NO_HORIZONTAL_CENTERING) != 0) { text_to_buf(windowBuf + backgroundWidth * v23 + xtable[dialogType], string, backgroundWidth, backgroundWidth, bodyColor); } else { int length = text_width(string); text_to_buf(windowBuf + backgroundWidth * v23 + (backgroundWidth - length) / 2, string, backgroundWidth, backgroundWidth, bodyColor); } v23 += text_height(); } } } win_draw(win); int rc = -1; while (rc == -1) { int keyCode = get_input(); if (keyCode == 500) { rc = 1; } else if (keyCode == KEY_RETURN) { gsound_play_sfx_file("ib1p1xx1"); rc = 1; } else if (keyCode == KEY_ESCAPE || keyCode == 501) { rc = 0; } else { if ((flags & 0x10) != 0) { if (keyCode == KEY_UPPERCASE_Y || keyCode == KEY_LOWERCASE_Y) { rc = 1; } else if (keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N) { rc = 0; } } } if (game_user_wants_to_quit != 0) { rc = 1; } } win_delete(win); art_ptr_unlock(backgroundHandle); text_font(savedFont); if (v86) { art_ptr_unlock(doneBoxHandle); art_ptr_unlock(downButtonHandle); art_ptr_unlock(upButtonHandle); message_exit(&messageList); } return rc; } // 0x41DE90 int file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags) { int oldFont = text_curr(); bool isScrollable = false; if (fileListLength > FILE_DIALOG_LINE_COUNT) { isScrollable = true; } int selectedFileIndex = 0; int pageOffset = 0; int maxPageOffset = fileListLength - (FILE_DIALOG_LINE_COUNT + 1); if (maxPageOffset < 0) { maxPageOffset = fileListLength - 1; if (maxPageOffset < 0) { maxPageOffset = 0; } } unsigned char* frmBuffers[FILE_DIALOG_FRM_COUNT]; CacheEntry* frmHandles[FILE_DIALOG_FRM_COUNT]; Size frmSizes[FILE_DIALOG_FRM_COUNT]; for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, flgids[index], 0, 0, 0); frmBuffers[index] = art_lock(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height)); if (frmBuffers[index] == NULL) { while (--index >= 0) { art_ptr_unlock(frmHandles[index]); } return -1; } } int backgroundWidth = frmSizes[FILE_DIALOG_FRM_BACKGROUND].width; int backgroundHeight = frmSizes[FILE_DIALOG_FRM_BACKGROUND].height; int win = win_add(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } return -1; } unsigned char* windowBuffer = win_get_buf(win); memcpy(windowBuffer, frmBuffers[FILE_DIALOG_FRM_BACKGROUND], backgroundWidth * backgroundHeight); MessageList messageList; MessageListItem messageListItem; if (!message_init(&messageList)) { win_delete(win); for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "DBOX.MSG"); if (!message_load(&messageList, path)) { win_delete(win); for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } return -1; } text_font(103); // DONE const char* done = getmsg(&messageList, &messageListItem, 100); text_to_buf(windowBuffer + LOAD_FILE_DIALOG_DONE_LABEL_Y * backgroundWidth + LOAD_FILE_DIALOG_DONE_LABEL_X, done, backgroundWidth, backgroundWidth, colorTable[18979]); // CANCEL const char* cancel = getmsg(&messageList, &messageListItem, 103); text_to_buf(windowBuffer + LOAD_FILE_DIALOG_CANCEL_LABEL_Y * backgroundWidth + LOAD_FILE_DIALOG_CANCEL_LABEL_X, cancel, backgroundWidth, backgroundWidth, colorTable[18979]); int doneBtn = win_register_button(win, LOAD_FILE_DIALOG_DONE_BUTTON_X, LOAD_FILE_DIALOG_DONE_BUTTON_Y, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height, -1, -1, -1, 500, frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL], frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); } int cancelBtn = win_register_button(win, LOAD_FILE_DIALOG_CANCEL_BUTTON_X, LOAD_FILE_DIALOG_CANCEL_BUTTON_Y, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height, -1, -1, -1, 501, frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL], frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (cancelBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } int scrollUpBtn = win_register_button(win, FILE_DIALOG_SCROLL_BUTTON_X, FILE_DIALOG_SCROLL_BUTTON_Y, frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].width, frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height, -1, 505, 506, 505, frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL], frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (scrollUpBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } int scrollDownButton = win_register_button(win, FILE_DIALOG_SCROLL_BUTTON_X, FILE_DIALOG_SCROLL_BUTTON_Y + frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height, frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].width, frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].height, -1, 503, 504, 503, frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL], frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (scrollUpBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } win_register_button( win, FILE_DIALOG_FILE_LIST_X, FILE_DIALOG_FILE_LIST_Y, FILE_DIALOG_FILE_LIST_WIDTH, FILE_DIALOG_FILE_LIST_HEIGHT, -1, -1, -1, 502, NULL, NULL, NULL, 0); if (title != NULL) { text_to_buf(windowBuffer + backgroundWidth * FILE_DIALOG_TITLE_Y + FILE_DIALOG_TITLE_X, title, backgroundWidth, backgroundWidth, colorTable[18979]); } text_font(101); PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); win_draw(win); int doubleClickSelectedFileIndex = -2; int doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY; int rc = -1; while (rc == -1) { unsigned int tick = get_time(); int keyCode = get_input(); int scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_NONE; int scrollCounter = 0; bool isScrolling = false; if (keyCode == 500) { if (fileListLength != 0) { strncpy(dest, fileList[selectedFileIndex + pageOffset], 16); rc = 0; } else { rc = 1; } } else if (keyCode == 501 || keyCode == KEY_ESCAPE) { rc = 1; } else if (keyCode == 502 && fileListLength != 0) { int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int selectedLine = (mouseY - y - FILE_DIALOG_FILE_LIST_Y) / text_height(); if (selectedLine - 1 < 0) { selectedLine = 0; } if (isScrollable || selectedLine < fileListLength) { if (selectedLine >= FILE_DIALOG_LINE_COUNT) { selectedLine = FILE_DIALOG_LINE_COUNT - 1; } } else { selectedLine = fileListLength - 1; } selectedFileIndex = selectedLine; if (selectedFileIndex == doubleClickSelectedFileIndex) { gsound_play_sfx_file("ib1p1xx1"); strncpy(dest, fileList[selectedFileIndex + pageOffset], 16); rc = 0; } doubleClickSelectedFileIndex = selectedFileIndex; PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); } else if (keyCode == 506) { scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_UP; } else if (keyCode == 504) { scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_DOWN; } else { switch (keyCode) { case KEY_ARROW_UP: pageOffset--; if (pageOffset < 0) { selectedFileIndex--; if (selectedFileIndex < 0) { selectedFileIndex = 0; } pageOffset = 0; } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; case KEY_ARROW_DOWN: if (isScrollable) { pageOffset++; // FIXME: Should be >= maxPageOffset (as in save dialog). // Otherwise out of bounds index is considered selected. if (pageOffset > maxPageOffset) { selectedFileIndex++; // FIXME: Should be >= FILE_DIALOG_LINE_COUNT (as in // save dialog). Otherwise out of bounds index is // considered selected. if (selectedFileIndex > FILE_DIALOG_LINE_COUNT) { selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1; } pageOffset = maxPageOffset; } } else { selectedFileIndex++; if (selectedFileIndex > maxPageOffset) { selectedFileIndex = maxPageOffset; } } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; case KEY_HOME: selectedFileIndex = 0; pageOffset = 0; PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; case KEY_END: if (isScrollable) { selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1; pageOffset = maxPageOffset; } else { selectedFileIndex = maxPageOffset; pageOffset = 0; } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; } } if (scrollDirection != FILE_DIALOG_SCROLL_DIRECTION_NONE) { unsigned int scrollDelay = 4; doubleClickSelectedFileIndex = -2; while (1) { unsigned int scrollTick = get_time(); scrollCounter += 1; if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) { isScrolling = true; if (scrollCounter > 14.4) { scrollDelay += 1; if (scrollDelay > 24) { scrollDelay = 24; } } if (scrollDirection == FILE_DIALOG_SCROLL_DIRECTION_UP) { pageOffset--; if (pageOffset < 0) { selectedFileIndex--; if (selectedFileIndex < 0) { selectedFileIndex = 0; } pageOffset = 0; } } else { if (isScrollable) { pageOffset++; if (pageOffset > maxPageOffset) { selectedFileIndex++; if (selectedFileIndex >= FILE_DIALOG_LINE_COUNT) { selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1; } pageOffset = maxPageOffset; } } else { selectedFileIndex++; if (selectedFileIndex > maxPageOffset) { selectedFileIndex = maxPageOffset; } } } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); win_draw(win); } unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24; while (elapsed_time(scrollTick) < delay) { } if (game_user_wants_to_quit != 0) { rc = 1; break; } int keyCode = get_input(); if (keyCode == 505 || keyCode == 503) { break; } } } else { win_draw(win); doubleClickTimer--; if (doubleClickTimer == 0) { doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY; doubleClickSelectedFileIndex = -2; } while (elapsed_time(tick) < (1000 / 24)) { } } if (game_user_wants_to_quit) { rc = 1; } } win_delete(win); for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } message_exit(&messageList); text_font(oldFont); return rc; } // 0x41EA78 int save_file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags) { int oldFont = text_curr(); bool isScrollable = false; if (fileListLength > FILE_DIALOG_LINE_COUNT) { isScrollable = true; } int selectedFileIndex = 0; int pageOffset = 0; int maxPageOffset = fileListLength - (FILE_DIALOG_LINE_COUNT + 1); if (maxPageOffset < 0) { maxPageOffset = fileListLength - 1; if (maxPageOffset < 0) { maxPageOffset = 0; } } unsigned char* frmBuffers[FILE_DIALOG_FRM_COUNT]; CacheEntry* frmHandles[FILE_DIALOG_FRM_COUNT]; Size frmSizes[FILE_DIALOG_FRM_COUNT]; for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, flgids2[index], 0, 0, 0); frmBuffers[index] = art_lock(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height)); if (frmBuffers[index] == NULL) { while (--index >= 0) { art_ptr_unlock(frmHandles[index]); } return -1; } } int backgroundWidth = frmSizes[FILE_DIALOG_FRM_BACKGROUND].width; int backgroundHeight = frmSizes[FILE_DIALOG_FRM_BACKGROUND].height; int win = win_add(x, y, backgroundWidth, backgroundHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } return -1; } unsigned char* windowBuffer = win_get_buf(win); memcpy(windowBuffer, frmBuffers[FILE_DIALOG_FRM_BACKGROUND], backgroundWidth * backgroundHeight); MessageList messageList; MessageListItem messageListItem; if (!message_init(&messageList)) { win_delete(win); for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "DBOX.MSG"); if (!message_load(&messageList, path)) { win_delete(win); for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } return -1; } text_font(103); // DONE const char* done = getmsg(&messageList, &messageListItem, 100); text_to_buf(windowBuffer + backgroundWidth * SAVE_FILE_DIALOG_DONE_LABEL_Y + SAVE_FILE_DIALOG_DONE_LABEL_X, done, backgroundWidth, backgroundWidth, colorTable[18979]); // CANCEL const char* cancel = getmsg(&messageList, &messageListItem, 103); text_to_buf(windowBuffer + backgroundWidth * SAVE_FILE_DIALOG_CANCEL_LABEL_Y + SAVE_FILE_DIALOG_CANCEL_LABEL_X, cancel, backgroundWidth, backgroundWidth, colorTable[18979]); int doneBtn = win_register_button(win, SAVE_FILE_DIALOG_DONE_BUTTON_X, SAVE_FILE_DIALOG_DONE_BUTTON_Y, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height, -1, -1, -1, 500, frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL], frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); } int cancelBtn = win_register_button(win, SAVE_FILE_DIALOG_CANCEL_BUTTON_X, SAVE_FILE_DIALOG_CANCEL_BUTTON_Y, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].width, frmSizes[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED].height, -1, -1, -1, 501, frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL], frmBuffers[FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (cancelBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } int scrollUpBtn = win_register_button(win, FILE_DIALOG_SCROLL_BUTTON_X, FILE_DIALOG_SCROLL_BUTTON_Y, frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].width, frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height, -1, 505, 506, 505, frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL], frmBuffers[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (scrollUpBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } int scrollDownButton = win_register_button(win, FILE_DIALOG_SCROLL_BUTTON_X, FILE_DIALOG_SCROLL_BUTTON_Y + frmSizes[FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED].height, frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].width, frmSizes[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED].height, -1, 503, 504, 503, frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL], frmBuffers[FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (scrollUpBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } win_register_button( win, FILE_DIALOG_FILE_LIST_X, FILE_DIALOG_FILE_LIST_Y, FILE_DIALOG_FILE_LIST_WIDTH, FILE_DIALOG_FILE_LIST_HEIGHT, -1, -1, -1, 502, NULL, NULL, NULL, 0); if (title != NULL) { text_to_buf(windowBuffer + backgroundWidth * FILE_DIALOG_TITLE_Y + FILE_DIALOG_TITLE_X, title, backgroundWidth, backgroundWidth, colorTable[18979]); } text_font(101); int cursorHeight = text_height(); int cursorWidth = text_width("_") - 4; PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); int fileNameLength = 0; char* pch = dest; while (*pch != '\0' && *pch != '.') { fileNameLength++; if (fileNameLength >= 12) { break; } } dest[fileNameLength] = '\0'; char fileNameCopy[32]; strncpy(fileNameCopy, dest, 32); int fileNameCopyLength = strlen(fileNameCopy); fileNameCopy[fileNameCopyLength + 1] = '\0'; fileNameCopy[fileNameCopyLength] = ' '; unsigned char* fileNameBufferPtr = windowBuffer + backgroundWidth * 190 + 57; buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100); text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]); win_draw(win); int blinkingCounter = 3; bool blink = false; int doubleClickSelectedFileIndex = -2; int doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY; int rc = -1; while (rc == -1) { unsigned int tick = get_time(); int keyCode = get_input(); int scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_NONE; int scrollCounter = 0; bool isScrolling = false; if (keyCode == 500) { rc = 0; } else if (keyCode == KEY_RETURN) { gsound_play_sfx_file("ib1p1xx1"); rc = 0; } else if (keyCode == 501 || keyCode == KEY_ESCAPE) { rc = 1; } else if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && fileNameCopyLength > 0) { buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100); fileNameCopy[fileNameCopyLength - 1] = ' '; fileNameCopy[fileNameCopyLength] = '\0'; text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]); fileNameCopyLength--; win_draw(win); } else if (keyCode < KEY_FIRST_INPUT_CHARACTER || keyCode > KEY_LAST_INPUT_CHARACTER || fileNameCopyLength >= 8) { if (keyCode == 502 && fileListLength != 0) { int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int selectedLine = (mouseY - y - FILE_DIALOG_FILE_LIST_Y) / text_height(); if (selectedLine - 1 < 0) { selectedLine = 0; } if (isScrollable || selectedLine < fileListLength) { if (selectedLine >= FILE_DIALOG_LINE_COUNT) { selectedLine = FILE_DIALOG_LINE_COUNT - 1; } } else { selectedLine = fileListLength - 1; } selectedFileIndex = selectedLine; if (selectedFileIndex == doubleClickSelectedFileIndex) { gsound_play_sfx_file("ib1p1xx1"); strncpy(dest, fileList[selectedFileIndex + pageOffset], 16); int index; for (index = 0; index < 12; index++) { if (dest[index] == '.' || dest[index] == '\0') { break; } } dest[index] = '\0'; rc = 2; } else { doubleClickSelectedFileIndex = selectedFileIndex; buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100); strncpy(fileNameCopy, fileList[selectedFileIndex + pageOffset], 16); int index; for (index = 0; index < 12; index++) { if (fileNameCopy[index] == '.' || fileNameCopy[index] == '\0') { break; } } fileNameCopy[index] = '\0'; fileNameCopyLength = strlen(fileNameCopy); fileNameCopy[fileNameCopyLength] = ' '; fileNameCopy[fileNameCopyLength + 1] = '\0'; text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]); PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); } } else if (keyCode == 506) { scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_UP; } else if (keyCode == 504) { scrollDirection = FILE_DIALOG_SCROLL_DIRECTION_DOWN; } else { switch (keyCode) { case KEY_ARROW_UP: pageOffset--; if (pageOffset < 0) { selectedFileIndex--; if (selectedFileIndex < 0) { selectedFileIndex = 0; } pageOffset = 0; } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; case KEY_ARROW_DOWN: if (isScrollable) { pageOffset++; if (pageOffset >= maxPageOffset) { selectedFileIndex++; if (selectedFileIndex >= FILE_DIALOG_LINE_COUNT) { selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1; } pageOffset = maxPageOffset; } } else { selectedFileIndex++; if (selectedFileIndex > maxPageOffset) { selectedFileIndex = maxPageOffset; } } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; case KEY_HOME: selectedFileIndex = 0; pageOffset = 0; PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; case KEY_END: if (isScrollable) { selectedFileIndex = 11; pageOffset = maxPageOffset; } else { selectedFileIndex = maxPageOffset; pageOffset = 0; } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); doubleClickSelectedFileIndex = -2; break; } } } else if (isdoschar(keyCode)) { buf_fill(fileNameBufferPtr, text_width(fileNameCopy), cursorHeight, backgroundWidth, 100); fileNameCopy[fileNameCopyLength] = keyCode & 0xFF; fileNameCopy[fileNameCopyLength + 1] = ' '; fileNameCopy[fileNameCopyLength + 2] = '\0'; text_to_buf(fileNameBufferPtr, fileNameCopy, backgroundWidth, backgroundWidth, colorTable[992]); fileNameCopyLength++; win_draw(win); } if (scrollDirection != FILE_DIALOG_SCROLL_DIRECTION_NONE) { unsigned int scrollDelay = 4; doubleClickSelectedFileIndex = -2; while (1) { unsigned int scrollTick = get_time(); scrollCounter += 1; if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) { isScrolling = true; if (scrollCounter > 14.4) { scrollDelay += 1; if (scrollDelay > 24) { scrollDelay = 24; } } if (scrollDirection == FILE_DIALOG_SCROLL_DIRECTION_UP) { pageOffset--; if (pageOffset < 0) { selectedFileIndex--; if (selectedFileIndex < 0) { selectedFileIndex = 0; } pageOffset = 0; } } else { if (isScrollable) { pageOffset++; if (pageOffset > maxPageOffset) { selectedFileIndex++; if (selectedFileIndex >= FILE_DIALOG_LINE_COUNT) { selectedFileIndex = FILE_DIALOG_LINE_COUNT - 1; } pageOffset = maxPageOffset; } } else { selectedFileIndex++; if (selectedFileIndex > maxPageOffset) { selectedFileIndex = maxPageOffset; } } } PrntFlist(windowBuffer, fileList, pageOffset, fileListLength, selectedFileIndex, backgroundWidth); win_draw(win); } // NOTE: Original code is slightly different. For unknown reason // entire blinking stuff is placed into two different branches, // which only differs by amount of delay. Probably result of // using large blinking macro as there are no traces of inlined // function. blinkingCounter -= 1; if (blinkingCounter == 0) { blinkingCounter = 3; int color = blink ? 100 : colorTable[992]; blink = !blink; buf_fill(fileNameBufferPtr + text_width(fileNameCopy) - cursorWidth, cursorWidth, cursorHeight - 2, backgroundWidth, color); } // FIXME: Missing windowRefresh makes blinking useless. unsigned int delay = (scrollCounter > 14.4) ? 1000 / scrollDelay : 1000 / 24; while (elapsed_time(scrollTick) < delay) { } if (game_user_wants_to_quit != 0) { rc = 1; break; } int key = get_input(); if (key == 505 || key == 503) { break; } } } else { blinkingCounter -= 1; if (blinkingCounter == 0) { blinkingCounter = 3; int color = blink ? 100 : colorTable[992]; blink = !blink; buf_fill(fileNameBufferPtr + text_width(fileNameCopy) - cursorWidth, cursorWidth, cursorHeight - 2, backgroundWidth, color); } win_draw(win); doubleClickTimer--; if (doubleClickTimer == 0) { doubleClickTimer = FILE_DIALOG_DOUBLE_CLICK_DELAY; doubleClickSelectedFileIndex = -2; } while (elapsed_time(tick) < (1000 / 24)) { } } if (game_user_wants_to_quit != 0) { rc = 1; } } if (rc == 0) { if (fileNameCopyLength != 0) { fileNameCopy[fileNameCopyLength] = '\0'; strcpy(dest, fileNameCopy); } else { rc = 1; } } else { if (rc == 2) { rc = 0; } } win_delete(win); for (int index = 0; index < FILE_DIALOG_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } message_exit(&messageList); text_font(oldFont); return rc; } // 0x41FBDC static void PrntFlist(unsigned char* buffer, char** fileList, int pageOffset, int fileListLength, int selectedIndex, int pitch) { int lineHeight = text_height(); int y = FILE_DIALOG_FILE_LIST_Y; buf_fill(buffer + y * pitch + FILE_DIALOG_FILE_LIST_X, FILE_DIALOG_FILE_LIST_WIDTH, FILE_DIALOG_FILE_LIST_HEIGHT, pitch, 100); if (fileListLength != 0) { if (fileListLength - pageOffset > FILE_DIALOG_LINE_COUNT) { fileListLength = FILE_DIALOG_LINE_COUNT; } for (int index = 0; index < fileListLength; index++) { int color = index == selectedIndex ? colorTable[32747] : colorTable[992]; text_to_buf(buffer + pitch * y + FILE_DIALOG_FILE_LIST_X, fileList[pageOffset + index], pitch, pitch, color); y += lineHeight; } } } ================================================ FILE: src/game/bmpdlog.h ================================================ #ifndef FALLOUT_GAME_BMPDLOG_H_ #define FALLOUT_GAME_BMPDLOG_H_ typedef enum DialogBoxOptions { DIALOG_BOX_LARGE = 0x01, DIALOG_BOX_MEDIUM = 0x02, DIALOG_BOX_NO_HORIZONTAL_CENTERING = 0x04, DIALOG_BOX_NO_VERTICAL_CENTERING = 0x08, DIALOG_BOX_YES_NO = 0x10, DIALOG_BOX_0x20 = 0x20, } DialogBoxOptions; typedef enum DialogType { DIALOG_TYPE_MEDIUM, DIALOG_TYPE_LARGE, DIALOG_TYPE_COUNT, } DialogType; typedef enum FileDialogFrm { FILE_DIALOG_FRM_BACKGROUND, FILE_DIALOG_FRM_LITTLE_RED_BUTTON_NORMAL, FILE_DIALOG_FRM_LITTLE_RED_BUTTON_PRESSED, FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_NORMAL, FILE_DIALOG_FRM_SCROLL_DOWN_ARROW_PRESSED, FILE_DIALOG_FRM_SCROLL_UP_ARROW_NORMAL, FILE_DIALOG_FRM_SCROLL_UP_ARROW_PRESSED, FILE_DIALOG_FRM_COUNT, } FileDialogFrm; typedef enum FileDialogScrollDirection { FILE_DIALOG_SCROLL_DIRECTION_NONE, FILE_DIALOG_SCROLL_DIRECTION_UP, FILE_DIALOG_SCROLL_DIRECTION_DOWN, } FileDialogScrollDirection; extern int dbox[DIALOG_TYPE_COUNT]; extern int ytable[DIALOG_TYPE_COUNT]; extern int xtable[DIALOG_TYPE_COUNT]; extern int doneY[DIALOG_TYPE_COUNT]; extern int doneX[DIALOG_TYPE_COUNT]; extern int dblines[DIALOG_TYPE_COUNT]; extern int flgids[FILE_DIALOG_FRM_COUNT]; extern int flgids2[FILE_DIALOG_FRM_COUNT]; int dialog_out(const char* title, const char** body, int bodyLength, int x, int y, int titleColor, const char* a8, int bodyColor, int flags); int file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags); int save_file_dialog(char* title, char** fileList, char* dest, int fileListLength, int x, int y, int flags); #endif /* FALLOUT_GAME_BMPDLOG_H_ */ ================================================ FILE: src/game/cache.c ================================================ #include "game/cache.h" #include #include #include #include #include "plib/gnw/debug.h" #include "plib/gnw/memory.h" #include "int/sound.h" static_assert(sizeof(CacheEntry) == 32, "wrong size"); static_assert(sizeof(Cache) == 84, "wrong size"); static bool cache_add(Cache* cache, int key, int* indexPtr); static bool cache_insert(Cache* cache, CacheEntry* cacheEntry, int index); static int cache_find(Cache* cache, int key, int* indexPtr); static int cache_create_item(CacheEntry** cacheEntryPtr); static bool cache_init_item(CacheEntry* cacheEntry); static bool cache_destroy_item(Cache* cache, CacheEntry* cacheEntry); static bool cache_unlock_all(Cache* cache); static bool cache_reset_counter(Cache* cache); static bool cache_make_room(Cache* cache, int size); static bool cache_purge(Cache* cache); static bool cache_resize_array(Cache* cache, int newCapacity); static int cache_compare_make_room(const void* a1, const void* a2); static int cache_compare_reset_counter(const void* a1, const void* a2); // 0x510938 static int lock_sound_ticker = 0; // cache_init // 0x41FCC0 bool cache_init(Cache* cache, CacheSizeProc* sizeProc, CacheReadProc* readProc, CacheFreeProc* freeProc, int maxSize) { if (!heap_init(&(cache->heap), maxSize)) { return false; } cache->size = 0; cache->maxSize = maxSize; cache->entriesLength = 0; cache->entriesCapacity = CACHE_ENTRIES_INITIAL_CAPACITY; cache->hits = 0; cache->entries = (CacheEntry**)mem_malloc(sizeof(*cache->entries) * cache->entriesCapacity); cache->sizeProc = sizeProc; cache->readProc = readProc; cache->freeProc = freeProc; if (cache->entries == NULL) { return false; } memset(cache->entries, 0, sizeof(*cache->entries) * cache->entriesCapacity); return true; } // cache_exit // 0x41FD50 bool cache_exit(Cache* cache) { if (cache == NULL) { return false; } cache_unlock_all(cache); cache_flush(cache); heap_exit(&(cache->heap)); cache->size = 0; cache->maxSize = 0; cache->entriesLength = 0; cache->entriesCapacity = 0; cache->hits = 0; if (cache->entries != NULL) { mem_free(cache->entries); cache->entries = NULL; } cache->sizeProc = NULL; cache->readProc = NULL; cache->freeProc = NULL; return true; } // NOTE: Unused. // // 0x41FDC0 int cache_query(Cache* cache, int key) { int index; if (cache == NULL) { return 0; } if (cache_find(cache, key, &index) != 2) { return 0; } return 1; } // 0x41FDE8 bool cache_lock(Cache* cache, int key, void** data, CacheEntry** cacheEntryPtr) { if (cache == NULL || data == NULL || cacheEntryPtr == NULL) { return false; } *cacheEntryPtr = NULL; int index; int rc = cache_find(cache, key, &index); if (rc == 2) { // Use existing cache entry. CacheEntry* cacheEntry = cache->entries[index]; cacheEntry->hits++; } else if (rc == 3) { // New cache entry is required. if (cache->entriesLength >= INT_MAX) { return false; } if (!cache_add(cache, key, &index)) { return false; } lock_sound_ticker %= 4; if (lock_sound_ticker == 0) { soundContinueAll(); } } else { return false; } CacheEntry* cacheEntry = cache->entries[index]; if (cacheEntry->referenceCount == 0) { if (!heap_lock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) { return false; } } cacheEntry->referenceCount++; cache->hits++; cacheEntry->mru = cache->hits; if (cache->hits == UINT_MAX) { cache_reset_counter(cache); } *data = cacheEntry->data; *cacheEntryPtr = cacheEntry; return true; } // 0x4200B8 bool cache_unlock(Cache* cache, CacheEntry* cacheEntry) { if (cache == NULL || cacheEntry == NULL) { return false; } if (cacheEntry->referenceCount == 0) { return false; } cacheEntry->referenceCount--; if (cacheEntry->referenceCount == 0) { heap_unlock(&(cache->heap), cacheEntry->heapHandleIndex); } return true; } // NOTE: Unused. // // 0x4200EC int cache_discard(Cache* cache, int key) { int index; CacheEntry* cacheEntry; if (cache == NULL) { return 0; } if (cache_find(cache, key, &index) != 2) { return 0; } cacheEntry = cache->entries[index]; if (cacheEntry->referenceCount != 0) { return 0; } cacheEntry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION; cache_purge(cache); return 1; } // cache_flush // 0x42012C bool cache_flush(Cache* cache) { if (cache == NULL) { return false; } // Loop thru cache entries and mark those with no references for eviction. for (int index = 0; index < cache->entriesLength; index++) { CacheEntry* cacheEntry = cache->entries[index]; if (cacheEntry->referenceCount == 0) { cacheEntry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION; } } // Sweep cache entries marked earlier. cache_purge(cache); // Shrink cache entries array if it's too big. int optimalCapacity = cache->entriesLength + CACHE_ENTRIES_GROW_CAPACITY; if (optimalCapacity < cache->entriesCapacity) { cache_resize_array(cache, optimalCapacity); } return true; } // NOTE: Unused. // // 0x420184 int cache_size(Cache* cache, int* sizePtr) { if (cache == NULL) { return 0; } if (sizePtr == NULL) { return 0; } *sizePtr = cache->size; return 1; } // 0x42019C bool cache_stats(Cache* cache, char* dest) { if (cache == NULL || dest == NULL) { return false; } sprintf(dest, "Cache stats are disabled.%s", "\n"); return true; } // NOTE: Unused. // // 0x4201C0 int cache_create_list(Cache* cache, unsigned int a2, int** tagsPtr, int* tagsLengthPtr) { int cacheItemIndex; int tagIndex; if (cache == NULL) { return 0; } if (tagsPtr == NULL) { return 0; } if (tagsLengthPtr == NULL) { return 0; } *tagsLengthPtr = 0; switch (a2) { case CACHE_LIST_REQUEST_TYPE_ALL_ITEMS: *tagsPtr = (int*)mem_malloc(sizeof(*tagsPtr) * cache->entriesLength); if (*tagsPtr == NULL) { return 0; } for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) { (*tagsPtr)[cacheItemIndex] = cache->entries[cacheItemIndex]->key; } *tagsLengthPtr = cache->entriesLength; break; case CACHE_LIST_REQUEST_TYPE_LOCKED_ITEMS: for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) { if (cache->entries[cacheItemIndex]->referenceCount != 0) { (*tagsLengthPtr)++; } } *tagsPtr = (int*)mem_malloc(sizeof(*tagsPtr) * (*tagsLengthPtr)); if (*tagsPtr == NULL) { return 0; } tagIndex = 0; for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) { if (cache->entries[cacheItemIndex]->referenceCount != 0) { if (tagIndex < *tagsLengthPtr) { (*tagsPtr)[tagIndex++] = cache->entries[cacheItemIndex]->key; } } } break; case CACHE_LIST_REQUEST_TYPE_UNLOCKED_ITEMS: for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) { if (cache->entries[cacheItemIndex]->referenceCount == 0) { (*tagsLengthPtr)++; } } *tagsPtr = (int*)mem_malloc(sizeof(*tagsPtr) * (*tagsLengthPtr)); if (*tagsPtr == NULL) { return 0; } tagIndex = 0; for (cacheItemIndex = 0; cacheItemIndex < cache->entriesLength; cacheItemIndex++) { if (cache->entries[cacheItemIndex]->referenceCount == 0) { if (tagIndex < *tagsLengthPtr) { (*tagsPtr)[tagIndex++] = cache->entries[cacheItemIndex]->key; } } } break; } return 1; } // NOTE: Unused. // // 0x420384 int cache_destroy_list(int** tagsPtr) { if (tagsPtr == NULL) { return 0; } if (*tagsPtr == NULL) { return 0; } mem_free(*tagsPtr); *tagsPtr = NULL; return 1; } // Fetches entry for the specified key into the cache. // // 0x4203AC static bool cache_add(Cache* cache, int key, int* indexPtr) { CacheEntry* cacheEntry; // NOTE: Uninline. if (cache_create_item(&cacheEntry) != 1) { return 0; } do { int size; if (cache->sizeProc(key, &size) != 0) { break; } if (!cache_make_room(cache, size)) { break; } bool allocated = false; int cacheEntrySize = size; for (int attempt = 0; attempt < 10; attempt++) { if (heap_allocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 1)) { allocated = true; break; } cacheEntrySize = (int)((double)cacheEntrySize + (double)size * 0.25); if (cacheEntrySize > cache->maxSize) { break; } if (!cache_make_room(cache, cacheEntrySize)) { break; } } if (!allocated) { cache_flush(cache); allocated = true; if (!heap_allocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 1)) { if (!heap_allocate(&(cache->heap), &(cacheEntry->heapHandleIndex), size, 0)) { allocated = false; } } } if (!allocated) { break; } do { if (!heap_lock(&(cache->heap), cacheEntry->heapHandleIndex, &(cacheEntry->data))) { break; } if (cache->readProc(key, &size, cacheEntry->data) != 0) { break; } heap_unlock(&(cache->heap), cacheEntry->heapHandleIndex); cacheEntry->size = size; cacheEntry->key = key; bool isNewKey = true; if (*indexPtr < cache->entriesLength) { if (key < cache->entries[*indexPtr]->key) { if (*indexPtr == 0 || key > cache->entries[*indexPtr - 1]->key) { isNewKey = false; } } } if (isNewKey) { if (cache_find(cache, key, indexPtr) != 3) { break; } } if (!cache_insert(cache, cacheEntry, *indexPtr)) { break; } return true; } while (0); heap_unlock(&(cache->heap), cacheEntry->heapHandleIndex); } while (0); // NOTE: Uninline. cache_destroy_item(cache, cacheEntry); return false; } // 0x4205E8 static bool cache_insert(Cache* cache, CacheEntry* cacheEntry, int index) { // Ensure cache have enough space for new entry. if (cache->entriesLength == cache->entriesCapacity - 1) { if (!cache_resize_array(cache, cache->entriesCapacity + CACHE_ENTRIES_GROW_CAPACITY)) { return false; } } // Move entries below insertion point. memmove(&(cache->entries[index + 1]), &(cache->entries[index]), sizeof(*cache->entries) * (cache->entriesLength - index)); cache->entries[index] = cacheEntry; cache->entriesLength++; cache->size += cacheEntry->size; return true; } // Finds index for given key. // // Returns 2 if entry already exists in cache, or 3 if entry does not exist. In // this case indexPtr represents insertion point. // // 0x420654 static int cache_find(Cache* cache, int key, int* indexPtr) { int length = cache->entriesLength; if (length == 0) { *indexPtr = 0; return 3; } int r = length - 1; int l = 0; int mid; int cmp; do { mid = (l + r) / 2; cmp = key - cache->entries[mid]->key; if (cmp == 0) { *indexPtr = mid; return 2; } if (cmp > 0) { l = l + 1; } else { r = r - 1; } } while (r >= l); if (cmp < 0) { *indexPtr = mid; } else { *indexPtr = mid + 1; } return 3; } // NOTE: Inlined. // // 0x4206C0 static int cache_create_item(CacheEntry** cacheEntryPtr) { *cacheEntryPtr = (CacheEntry*)mem_malloc(sizeof(**cacheEntryPtr)); // FIXME: Wrong check, should be *cacheEntryPtr != NULL. if (cacheEntryPtr != NULL) { // NOTE: Uninline. return cache_init_item(*cacheEntryPtr); } return 0; } // 0x420708 static bool cache_init_item(CacheEntry* cacheEntry) { cacheEntry->key = 0; cacheEntry->size = 0; cacheEntry->data = NULL; cacheEntry->referenceCount = 0; cacheEntry->hits = 0; cacheEntry->flags = 0; cacheEntry->mru = 0; return true; } // NOTE: Inlined. // // 0x420740 static bool cache_destroy_item(Cache* cache, CacheEntry* cacheEntry) { if (cacheEntry->data != NULL) { heap_deallocate(&(cache->heap), &(cacheEntry->heapHandleIndex)); } mem_free(cacheEntry); return true; } // 0x420764 static bool cache_unlock_all(Cache* cache) { Heap* heap = &(cache->heap); for (int index = 0; index < cache->entriesLength; index++) { CacheEntry* cacheEntry = cache->entries[index]; // NOTE: Original code is slightly different. For unknown reason it uses // inner loop to decrement `referenceCount` one by one. Probably using // some inlined function. if (cacheEntry->referenceCount != 0) { heap_unlock(heap, cacheEntry->heapHandleIndex); cacheEntry->referenceCount = 0; } } return true; } // 0x4207D4 static bool cache_reset_counter(Cache* cache) { if (cache == NULL) { return false; } CacheEntry** entries = (CacheEntry**)mem_malloc(sizeof(*entries) * cache->entriesLength); if (entries == NULL) { return false; } memcpy(entries, cache->entries, sizeof(*entries) * cache->entriesLength); qsort(entries, cache->entriesLength, sizeof(*entries), cache_compare_reset_counter); for (int index = 0; index < cache->entriesLength; index++) { CacheEntry* cacheEntry = entries[index]; cacheEntry->mru = index; } cache->hits = cache->entriesLength; // FIXME: Obviously leak `entries`. return true; } // Prepare cache for storing new entry with the specified size. // // 0x42084C static bool cache_make_room(Cache* cache, int size) { if (size > cache->maxSize) { // The entry of given size is too big for caching, no matter what. return false; } if (cache->maxSize - cache->size >= size) { // There is space available for entry of given size, there is no need to // evict anything. return true; } CacheEntry** entries = (CacheEntry**)mem_malloc(sizeof(*entries) * cache->entriesLength); if (entries != NULL) { memcpy(entries, cache->entries, sizeof(*entries) * cache->entriesLength); qsort(entries, cache->entriesLength, sizeof(*entries), cache_compare_make_room); // The sweeping threshold is 20% of cache size plus size for the new // entry. Once the threshold is reached the marking process stops. int threshold = size + (int)((double)cache->size * 0.2); int accum = 0; int index; for (index = 0; index < cache->entriesLength; index++) { CacheEntry* entry = entries[index]; if (entry->referenceCount == 0) { if (entry->size >= threshold) { entry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION; // We've just found one huge entry, there is no point to // mark individual smaller entries in the code path below, // reset the accumulator to skip it entirely. accum = 0; break; } else { accum += entry->size; if (accum >= threshold) { break; } } } } if (accum != 0) { // The loop below assumes index to be positioned on the entry, where // accumulator stopped. If we've reached the end, reposition // it to the last entry. if (index == cache->entriesLength) { index -= 1; } // Loop backwards from the point we've stopped and mark all // unreferenced entries for sweeping. for (; index >= 0; index--) { CacheEntry* entry = entries[index]; if (entry->referenceCount == 0) { entry->flags |= CACHE_ENTRY_MARKED_FOR_EVICTION; } } } mem_free(entries); } cache_purge(cache); if (cache->maxSize - cache->size >= size) { return true; } return false; } // 0x42099C static bool cache_purge(Cache* cache) { for (int index = 0; index < cache->entriesLength; index++) { CacheEntry* cacheEntry = cache->entries[index]; if ((cacheEntry->flags & CACHE_ENTRY_MARKED_FOR_EVICTION) != 0) { if (cacheEntry->referenceCount != 0) { // Entry was marked for eviction but still has references, // unmark it. cacheEntry->flags &= ~CACHE_ENTRY_MARKED_FOR_EVICTION; } else { int cacheEntrySize = cacheEntry->size; // NOTE: Uninline. cache_destroy_item(cache, cacheEntry); // Move entries up. memmove(&(cache->entries[index]), &(cache->entries[index + 1]), sizeof(*cache->entries) * ((cache->entriesLength - index) - 1)); cache->entriesLength--; cache->size -= cacheEntrySize; // The entry was removed, compensate index. index--; } } } return true; } // 0x420A40 static bool cache_resize_array(Cache* cache, int newCapacity) { if (newCapacity < cache->entriesLength) { return false; } CacheEntry** entries = (CacheEntry**)mem_realloc(cache->entries, sizeof(*cache->entries) * newCapacity); if (entries == NULL) { return false; } cache->entries = entries; cache->entriesCapacity = newCapacity; return true; } // 0x420A74 static int cache_compare_make_room(const void* a1, const void* a2) { CacheEntry* v1 = *(CacheEntry**)a1; CacheEntry* v2 = *(CacheEntry**)a2; if (v1->referenceCount != 0 && v2->referenceCount == 0) { return 1; } if (v2->referenceCount != 0 && v1->referenceCount == 0) { return -1; } if (v1->hits < v2->hits) { return -1; } else if (v1->hits > v2->hits) { return 1; } if (v1->mru < v2->mru) { return -1; } else if (v1->mru > v2->mru) { return 1; } return 0; } // 0x420AE8 static int cache_compare_reset_counter(const void* a1, const void* a2) { CacheEntry* v1 = *(CacheEntry**)a1; CacheEntry* v2 = *(CacheEntry**)a2; if (v1->mru < v2->mru) { return 1; } else if (v1->mru > v2->mru) { return -1; } else { return 0; } } ================================================ FILE: src/game/cache.h ================================================ #ifndef FALLOUT_GAME_CACHE_H_ #define FALLOUT_GAME_CACHE_H_ #include #include "game/heap.h" #define INVALID_CACHE_ENTRY ((CacheEntry*)-1) // The initial number of cache entries in new cache. #define CACHE_ENTRIES_INITIAL_CAPACITY 100 // The number of cache entries added when cache capacity is reached. #define CACHE_ENTRIES_GROW_CAPACITY 50 typedef enum CacheEntryFlags { // Specifies that cache entry has no references as should be evicted during // the next sweep operation. CACHE_ENTRY_MARKED_FOR_EVICTION = 0x01, } CacheEntryFlags; typedef enum CacheListRequestType { CACHE_LIST_REQUEST_TYPE_ALL_ITEMS = 0, CACHE_LIST_REQUEST_TYPE_LOCKED_ITEMS = 1, CACHE_LIST_REQUEST_TYPE_UNLOCKED_ITEMS = 2, } CacheListRequestType; typedef int CacheSizeProc(int key, int* sizePtr); typedef int CacheReadProc(int key, int* sizePtr, unsigned char* buffer); typedef void CacheFreeProc(void* ptr); typedef struct CacheEntry { int key; int size; unsigned char* data; unsigned int referenceCount; // Total number of hits that this cache entry received during it's // lifetime. unsigned int hits; unsigned int flags; // The most recent hit in terms of cache hit counter. Used to track most // recently used entries in eviction strategy. unsigned int mru; int heapHandleIndex; } CacheEntry; typedef struct Cache { // Current size of entries in cache. int size; // Maximum size of entries in cache. int maxSize; // The length of `entries` array. int entriesLength; // The capacity of `entries` array. int entriesCapacity; // Total number of hits during cache lifetime. unsigned int hits; // List of cache entries. CacheEntry** entries; CacheSizeProc* sizeProc; CacheReadProc* readProc; CacheFreeProc* freeProc; Heap heap; } Cache; bool cache_init(Cache* cache, CacheSizeProc* sizeProc, CacheReadProc* readProc, CacheFreeProc* freeProc, int maxSize); bool cache_exit(Cache* cache); int cache_query(Cache* cache, int key); bool cache_lock(Cache* cache, int key, void** data, CacheEntry** cacheEntryPtr); bool cache_unlock(Cache* cache, CacheEntry* cacheEntry); int cache_discard(Cache* cache, int key); bool cache_flush(Cache* cache); int cache_size(Cache* cache, int* sizePtr); bool cache_stats(Cache* cache, char* dest); int cache_create_list(Cache* cache, unsigned int a2, int** tagsPtr, int* tagsLengthPtr); int cache_destroy_list(int** tagsPtr); #endif /* FALLOUT_GAME_CACHE_H_ */ ================================================ FILE: src/game/cd.c ================================================ #include "game/cd.h" // NOTE: Actual file name is unknown. Functions in this module do not present // in debug symbols from `mapper2.exe`, and does not appear in OS X binary. The // functions implement some sort of CD check, and they appear between `cache.c` // and `combat.c`, so based in it's intent and order `cd.c` is a nice candidate. // // Since there are no visibility hints in Windows binary, all functions are // public. #include #define WIN32_LEAN_AND_MEAN #include // 0x420B10 int sub_420B10(const char* a1) { return GetDriveTypeA(a1) == DRIVE_CDROM; } // 0x420B28 int sub_420B28(const char* a1, const char* a2) { UINT oldErrorMode; char volumeName[MAX_PATH]; BOOL success; oldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); success = GetVolumeInformationA(a1, volumeName, MAX_PATH, NULL, NULL, NULL, NULL, 0); if (success) { success = lstrcmpA(a2, volumeName) == 0; } SetErrorMode(oldErrorMode); return success; } // 0x420B8C int sub_420B8C(const char* a1) { DWORD attributes; attributes = GetFileAttributesA(a1); if (attributes != INVALID_FILE_ATTRIBUTES) { if ((attributes & FILE_ATTRIBUTE_READONLY) != 0) { if (!SetFileAttributesA(a1, FILE_ATTRIBUTE_NORMAL)) { return 1; } SetFileAttributesA(a1, attributes); } } return 0; } // 0x420BDC int sub_420BDC(const char* a1, int a2) { DWORD sectorsPerCluster; DWORD bytesPerSector; DWORD numberOfFreeClusters; DWORD totalNumberOfClusters; if (GetDiskFreeSpaceA(a1, §orsPerCluster, &bytesPerSector, &numberOfFreeClusters, &totalNumberOfClusters)) { if (a2 == bytesPerSector) { return 1; } } return 0; } // 0x420C18 int sub_420C18(const char* a1, const char* a2, int a3) { char drive[_MAX_DRIVE + 1]; _splitpath(a1, drive, NULL, NULL, NULL); lstrcatA(drive, "\\"); if ((a3 & 0x1) != 0) { // NOTE: Uninline. if (!sub_420B10(drive)) { return 0; } } if ((a3 & 0x02) != 0) { if (a2 != NULL) { if (!sub_420B28(drive, a2)) { return 0; } } } if ((a3 & 0x04) != 0) { if (!sub_420B8C(a1)) { return 0; } } if ((a3 & 0x08) != 0) { if (!sub_420BDC(drive, 2048)) { return 0; } } return 1; } ================================================ FILE: src/game/cd.h ================================================ #ifndef FALLOUT_GAME_CD_H_ #define FALLOUT_GAME_CD_H_ int sub_420B10(const char* a1); int sub_420B28(const char* a1, const char* a2); int sub_420B8C(const char* a1); int sub_420BDC(const char* a1, int a2); int sub_420C18(const char* a1, const char* a2, int a3); #endif /* FALLOUT_GAME_CD_H_ */ ================================================ FILE: src/game/combat.c ================================================ #include "game/combat.h" #include #include #include #include "game/actions.h" #include "game/anim.h" #include "game/art.h" #include "plib/color/color.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/elevator.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/loadsave.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/perk.h" #include "game/pipboy.h" #include "game/proto.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "game/trait.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #define CALLED_SHOT_WINDOW_X 68 #define CALLED_SHOT_WINDOW_Y 20 #define CALLED_SHOT_WINDOW_WIDTH 504 #define CALLED_SHOT_WINDOW_HEIGHT 309 static void combatInitAIInfoList(); static int combatCopyAIInfo(int srcIndex, int destIndex); static void combat_begin(Object* a1); static void combat_begin_extra(Object* a1); static void combat_update_critters_in_los(int a1); static void combat_over(); static void combat_add_noncoms(); static int compare_faster(const void* a1, const void* a2); static void combat_sequence_init(Object* a1, Object* a2); static void combat_sequence(); static int combat_input(); static void combat_set_move_all(); static int combat_turn(Object* a1, bool a2); static bool combat_should_end(); static bool check_ranged_miss(Attack* attack); static int shoot_along_path(Attack* attack, int a2, int a3, int anim); static int compute_spray(Attack* attack, int accuracy, int* roundsHitMainTargetPtr, int* roundsSpentPtr, int anim); static int correctAttackForPerks(Attack* attack); static int compute_attack(Attack* attack); static int attack_crit_success(Attack* a1); static int attackFindInvalidFlags(Object* a1, Object* a2); static int attack_crit_failure(Attack* attack); static void do_random_cripple(int* flagsPtr); static int determine_to_hit_func(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, int a6); static void compute_damage(Attack* attack, int ammoQuantity, int bonusDamageMultiplier); static void check_for_death(Object* a1, int a2, int* a3); static void set_new_results(Object* a1, int a2); static void damage_object(Object* a1, int damage, bool animated, int a4, Object* a5); static void combat_display_hit(char* dest, Object* critter_obj, int damage); static void combat_display_flags(char* a1, int flags, Object* a3); static void combat_standup(Object* a1); static void print_tohit(unsigned char* dest, int dest_pitch, int a3); static char* combat_get_loc_name(Object* critter, int hitLocation); static void draw_loc_off(int a1, int a2); static void draw_loc_on(int a1, int a2); static void draw_loc(int eventCode, int color); static int get_called_shot_location(Object* critter, int* hitLocation, int hitMode); // TODO: Remove. // // 0x500B50 static char _a_1[] = "."; // 0x51093C static int combat_turn_running = 0; // 0x510940 int combatNumTurns = 0; // 0x510944 unsigned int combat_state = COMBAT_STATE_0x02; // 0x510948 static CombatAiInfo* aiInfoList = NULL; // 0x51094C STRUCT_664980* gcsd = NULL; // 0x510950 bool combat_call_display = false; // Accuracy modifiers for hit locations. // // 0x510954 static int hit_location_penalty[HIT_LOCATION_COUNT] = { -40, -30, -30, 0, -20, -20, -60, -30, 0, }; // Critical hit tables for every kill type. // // 0x510978 static CriticalHitDescription crit_succ_eff[KILL_TYPE_COUNT][HIT_LOCATION_COUNT][CRTICIAL_EFFECT_COUNT] = { // KILL_TYPE_MAN { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5002, 5003 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5002, 5003 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5004, 5003 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5005, 5006 }, { 6, DAM_DEAD, -1, 0, 0, 5007, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5008, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 5009, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 5010, 5011 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5012, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5012, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5013, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5008, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 5009, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 5014, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5015, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5015, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5013, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5016, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5019, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5019, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5020, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5021, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5023, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5025, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5025, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5026, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5023, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5025, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5025, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5026, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 4, DAM_BLIND, 5027, 5028 }, { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 5029, 5028 }, { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 5029, 5028 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5030, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5031, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 5032, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5033, 5000 }, { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5034, 5035 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5035, 5036 }, { 3, DAM_KNOCKED_OUT, -1, 0, 0, 5036, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5035, 5036 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5037, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5016, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 }, { 4, 0, -1, 0, 0, 5018, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5019, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5020, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5021, 5000 }, }, }, // KILL_TYPE_WOMAN { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5101, 5100 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5102, 5103 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5102, 5103 }, { 6, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5104, 5103 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5105, 5106 }, { 6, DAM_DEAD, -1, 0, 0, 5107, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5108, 5100 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 5109, 5100 }, { 4, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5110, 5111 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5110, 5111 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5112, 5100 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5113, 5100 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5108, 5100 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 5109, 5100 }, { 4, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5114, 5100 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5114, 5100 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5115, 5100 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5113, 5100 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5116, 5100 }, { 3, DAM_BYPASS, -1, 0, 0, 5117, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5120, 5100 }, { 6, DAM_DEAD, -1, 0, 0, 5121, 5100 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5123, 5100 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5123, 5124 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5123, 5124 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5125, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5125, 5126 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5126, 5100 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5123, 5100 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5123, 5124 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5123, 5124 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5125, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5125, 5126 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5126, 5100 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 4, DAM_BLIND, 5127, 5128 }, { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 5129, 5128 }, { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 5129, 5128 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5130, 5100 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5131, 5100 }, { 8, DAM_DEAD, -1, 0, 0, 5132, 5100 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5133, 5100 }, { 3, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5133, 5134 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5134, 5135 }, { 3, DAM_KNOCKED_OUT, -1, 0, 0, 5135, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5134, 5135 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5135, 5100 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5116, 5100 }, { 3, DAM_BYPASS, -1, 0, 0, 5117, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5119, 5100 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5120, 5100 }, { 6, DAM_DEAD, -1, 0, 0, 5121, 5100 }, }, }, // KILL_TYPE_CHILD { // HIT_LOCATION_HEAD { { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5200, 5201 }, { 4, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_OUT, 5202, 5203 }, { 4, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_OUT, 5202, 5203 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5203, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5203, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5204, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5205, 5000 }, { 4, DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5206, 5207 }, { 4, DAM_LOSE_TURN, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5206, 5207 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5208, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5208, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5208, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5209, 5000 }, { 4, DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5206, 5207 }, { 4, DAM_LOSE_TURN, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5206, 5207 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5208, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5208, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5208, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5210, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5211, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5212, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5212, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5213, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5214, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5215, 5000 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5217, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5217, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5217, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5215, 5000 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, DAM_CRIP_ARM_RIGHT | DAM_BLIND | DAM_ON_FIRE | DAM_EXPLODE, 5000, 0 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5217, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5217, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5217, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 5, DAM_BLIND, 5218, 5219 }, { 4, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 5220, 5221 }, { 6, DAM_BYPASS, STAT_LUCK, -1, DAM_BLIND, 5220, 5221 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5222, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5223, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 5224, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5225, 5000 }, { 3, 0, -1, 0, 0, 5225, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5226, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5210, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5211, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5211, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5212, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5213, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5214, 5000 }, }, }, // KILL_TYPE_SUPER_MUTANT { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5300, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, -1, DAM_KNOCKED_DOWN, 5301, 5302 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN, 5301, 5302 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5302, 5303 }, { 6, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5302, 5303 }, { 6, DAM_DEAD, -1, 0, 0, 5304, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_LOSE_TURN, 5300, 5306 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -1, DAM_CRIP_ARM_LEFT, 5307, 5308 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 5307, 5308 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5308, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5308, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_LOSE_TURN, 5300, 5006 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -1, DAM_CRIP_ARM_RIGHT, 5307, 5309 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 5307, 5309 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5309, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5309, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5301, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5310, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5311, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5300, 5312 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5312, 5313 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5313, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5314, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5315, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5300, 5312 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5312, 5313 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5313, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5314, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5315, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5300, 5000 }, { 4, DAM_BYPASS, STAT_LUCK, 5, DAM_BLIND, 5316, 5317 }, { 6, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 5316, 5317 }, { 6, DAM_BYPASS | DAM_LOSE_TURN, STAT_LUCK, 0, DAM_BLIND, 5318, 5319 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5320, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 5321, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, 0, STAT_LUCK, 0, DAM_BYPASS, 5300, 5017 }, { 3, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_DOWN, 5301, 5302 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5312, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5302, 5303 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5303, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5300, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5301, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5302, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5310, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5311, 5000 }, }, }, // KILL_TYPE_GHOUL { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5400, 5003 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -1, DAM_KNOCKED_OUT, 5400, 5003 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -2, DAM_KNOCKED_OUT, 5004, 5005 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_STRENGTH, 0, 0, 5005, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5401, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5016, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_DROP | DAM_LOSE_TURN, 5001, 5402 }, { 4, DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5402, 5012 }, { 4, DAM_BYPASS | DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5403, 5404 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5016, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_DROP | DAM_LOSE_TURN, 5001, 5402 }, { 4, DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5402, 5015 }, { 4, DAM_BYPASS | DAM_DROP | DAM_LOSE_TURN, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5403, 5404 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS | DAM_DROP, -1, 0, 0, 5404, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 }, { 3, 0, -1, 0, 0, 5018, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5003, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5007, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5023 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5024, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5024, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5026, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5023 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5024, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5024, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5026, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 3, DAM_BLIND, 5001, 5405 }, { 4, DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5406, 5407 }, { 6, DAM_BYPASS, STAT_LUCK, -3, DAM_BLIND, 5406, 5407 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5030, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5031, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 5408, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_LUCK, 0, DAM_BYPASS, 5001, 5033 }, { 3, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5033, 5035 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5004, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5035, 5036 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5036, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5017, 5000 }, { 3, 0, -1, 0, 0, 5018, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 5003, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5007, 5000 }, }, }, // KILL_TYPE_BRAHMIN { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 5, 0, STAT_ENDURANCE, 2, DAM_KNOCKED_DOWN, 5016, 5500 }, { 5, 0, STAT_ENDURANCE, -1, DAM_KNOCKED_DOWN, 5016, 5500 }, { 6, DAM_KNOCKED_OUT, STAT_STRENGTH, 0, 0, 5501, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5502, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5504, 5000 }, { 3, 0, -1, 0, 0, 5504, 5000 }, { 4, 0, -1, 0, 0, 5504, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5506, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5016, 5503 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5503, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5016, 5503 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5503, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 5029, 5507 }, { 6, DAM_BYPASS, STAT_LUCK, -3, DAM_BLIND, 5029, 5507 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5508, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5509, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 5510, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5511, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5511, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5512, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5512, 5000 }, { 6, DAM_BYPASS, -1, 0, 0, 5513, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5504, 5000 }, { 3, 0, -1, 0, 0, 5504, 5000 }, { 4, 0, -1, 0, 0, 5504, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5505, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5506, 5000 }, }, }, // KILL_TYPE_RADSCORPION { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 3, DAM_KNOCKED_DOWN, 5001, 5600 }, { 5, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5001, 5600 }, { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5001, 5600 }, { 6, DAM_KNOCKED_DOWN, -1, 0, 0, 5600, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5601, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5016, 5602 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5602, 5000 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5602, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, 2, DAM_CRIP_ARM_RIGHT, 5016, 5603 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5016, 5603 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5603, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5604, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5605, 5000 }, { 4, DAM_BYPASS, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5605, 5606 }, { 4, DAM_DEAD, -1, 0, 0, 5607, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 2, 0, 5001, 5600 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5600, 5608 }, { 4, DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5609, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5608, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 5608, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 2, 0, 5001, 5600 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5600, 5008 }, { 4, DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5609, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5608, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 5608, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_AGILITY, 3, DAM_BLIND, 5001, 5610 }, { 6, 0, STAT_AGILITY, 0, DAM_BLIND, 5016, 5610 }, { 6, 0, STAT_AGILITY, -3, DAM_BLIND, 5016, 5610 }, { 8, 0, STAT_AGILITY, -3, DAM_BLIND, 5611, 5612 }, { 8, DAM_DEAD, -1, 0, 0, 5613, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5614, 5000 }, { 3, 0, -1, 0, 0, 5614, 5000 }, { 4, 0, -1, 0, 0, 5614, 5000 }, { 4, DAM_KNOCKED_OUT, -1, 0, 0, 5615, 5000 }, { 4, DAM_KNOCKED_OUT, -1, 0, 0, 5615, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5616, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 5604, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5605, 5000 }, { 4, DAM_BYPASS, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5605, 5606 }, { 4, DAM_DEAD, -1, 0, 0, 5607, 5000 }, }, }, // KILL_TYPE_RAT { // HIT_LOCATION_HEAD { { 4, DAM_BYPASS, -1, 0, 0, 5700, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5700, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5701, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5701, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5701, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5701, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 }, { 3, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 }, { 3, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5703, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 }, { 3, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 }, { 3, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5705, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5706, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5708, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 }, { 3, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 }, { 3, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5709, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 }, { 3, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 }, { 3, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 5710, 5000 }, }, // HIT_LOCATION_EYES { { 4, DAM_BYPASS, -1, 0, 0, 5711, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5712, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5712, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5711, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5711, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 5712, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5706, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5707, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5708, 5000 }, }, }, // KILL_TYPE_FLOATER { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5800 }, { 5, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 5800 }, { 5, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 5800, 5801 }, { 6, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 5800, 5801 }, { 6, DAM_DEAD, -1, 0, 0, 5802, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5001, 5803 }, { 4, 0, STAT_ENDURANCE, -2, DAM_LOSE_TURN, 5001, 5803 }, { 3, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5001, 5803 }, { 4, 0, STAT_ENDURANCE, -2, DAM_LOSE_TURN, 5001, 5803 }, { 3, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5804, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5800 }, { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 5800 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5800, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5804, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 1, DAM_KNOCKED_DOWN, 5001, 5800 }, { 4, 0, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 5001, 5800 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -1, DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT, 5800, 5806 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT, 5804, 5806 }, { 6, DAM_DEAD | DAM_ON_FIRE, -1, 0, 0, 5807, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_LOSE_TURN, -1, 0, 0, 5803, 5000 }, { 4, DAM_LOSE_TURN, -1, 0, 0, 5803, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 5808, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 5808, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5809, 5000 }, { 5, 0, STAT_ENDURANCE, 0, DAM_BLIND, 5016, 5810 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_BLIND, 5809, 5810 }, { 6, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5810, 5000 }, { 6, DAM_KNOCKED_DOWN | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5801, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5001, 5800 }, { 3, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5001, 5800 }, { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5800, 5000 }, { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5800, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5800 }, { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 5800 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 5800, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5804, 5000 }, { 4, DAM_DEAD, -1, 0, 0, 5805, 5000 }, }, }, // KILL_TYPE_CENTAUR { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5900 }, { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5900 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5901, 5900 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5901, 5900 }, { 6, DAM_DEAD, -1, 0, 0, 5902, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5016, 5903 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5016, 5904 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 5904, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 5905, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 5016, 5903 }, { 4, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5016, 5904 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 5904, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 5905, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5901, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 2, 0, 5901, 5900 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5900, 5000 }, { 5, DAM_DEAD, -1, 0, 0, 5902, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5900, 5000 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5900, 5906 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5906, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 5906, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 5907, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 5900, 5000 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5900, 5906 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5906, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 5906, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_LOSE_TURN, -1, 0, 0, 5907, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 1, DAM_BLIND, 5001, 5908 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -1, DAM_BLIND, 5901, 5908 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5909, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 5910, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 5911, 5000 }, }, // HIT_LOCATION_GROIN { { 2, 0, -1, 0, 0, 5912, 5000 }, { 2, 0, -1, 0, 0, 5912, 5000 }, { 2, 0, -1, 0, 0, 5912, 5000 }, { 2, 0, -1, 0, 0, 5912, 5000 }, { 2, 0, -1, 0, 0, 5912, 5000 }, { 2, 0, -1, 0, 0, 5912, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5901, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 2, 0, 5901, 5900 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5900, 5000 }, { 5, DAM_DEAD, -1, 0, 0, 5902, 5000 }, }, }, // KILL_TYPE_ROBOT { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 6000, 5000 }, { 4, 0, -1, 0, 0, 6000, 5000 }, { 5, 0, -1, 0, 0, 6000, 5000 }, { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 6001, 5000 }, { 6, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6002, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6003, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 6000, 6004 }, { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6000, 6004 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 6004, 5000 }, { 4, DAM_CRIP_ARM_LEFT, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 6004, 6005 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 6000, 6004 }, { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6000, 6004 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 6004, 5000 }, { 4, DAM_CRIP_ARM_RIGHT, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 6004, 6005 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6006, 5000 }, { 4, 0, -1, 0, 0, 6007, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6008, 5000 }, { 6, DAM_BYPASS, -1, 0, 0, 6009, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6010, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 6000, 5000 }, { 4, 0, -1, 0, 0, 6007, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6000, 6004 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_LEG_RIGHT, 6007, 6004 }, { 4, DAM_CRIP_LEG_RIGHT, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6004, 6011 }, { 4, DAM_CRIP_LEG_RIGHT, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, 6004, 6012 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 6000, 5000 }, { 4, 0, -1, 0, 0, 6007, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6000, 6004 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_LEG_LEFT, 6007, 6004 }, { 4, DAM_CRIP_LEG_LEFT, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6004, 6011 }, { 4, DAM_CRIP_LEG_LEFT, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, 6004, 6012 }, }, // HIT_LOCATION_EYES { { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_BLIND, 6000, 6013 }, { 3, 0, STAT_ENDURANCE, -2, DAM_BLIND, 6000, 6013 }, { 3, 0, STAT_ENDURANCE, -4, DAM_BLIND, 6000, 6013 }, { 3, 0, STAT_ENDURANCE, -6, DAM_BLIND, 6000, 6013 }, { 3, DAM_BLIND, -1, 0, 0, 6013, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, 0, STAT_ENDURANCE, -1, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 6000, 6002 }, { 3, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 6000, 6002 }, { 3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, STAT_ENDURANCE, 0, 0, 6002, 6003 }, { 3, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, STAT_ENDURANCE, -4, 0, 6002, 6003 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 6000, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6006, 5000 }, { 4, 0, -1, 0, 0, 6007, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6008, 5000 }, { 6, DAM_BYPASS, -1, 0, 0, 6009, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6010, 5000 }, }, }, // KILL_TYPE_DOG { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5016, 6100 }, { 4, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5016, 6100 }, { 4, 0, STAT_ENDURANCE, -6, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT, 5016, 6101 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6100, 6102 }, { 4, DAM_DEAD, -1, 0, 0, 6103, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, -1, DAM_CRIP_LEG_LEFT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, 5001, 6105 }, { 3, DAM_CRIP_LEG_LEFT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 6105, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, -1, DAM_CRIP_LEG_RIGHT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, 5001, 6105 }, { 3, DAM_CRIP_LEG_RIGHT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 6105, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 5001, 6100 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 6100 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 6100, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6103, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, STAT_ENDURANCE, 1, DAM_CRIP_LEG_RIGHT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_LEG_RIGHT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, 5001, 6105 }, { 3, DAM_CRIP_LEG_RIGHT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 6105, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, STAT_ENDURANCE, 1, DAM_CRIP_LEG_LEFT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_LEG_LEFT, 5001, 6104 }, { 3, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, 5001, 6105 }, { 3, DAM_CRIP_LEG_LEFT, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 6104, 6105 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 6105, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 5018, 5000 }, { 6, DAM_BYPASS, -1, 0, 0, 5018, 5000 }, { 6, DAM_BYPASS, STAT_ENDURANCE, 3, DAM_BLIND, 5018, 6106 }, { 8, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_BLIND, 5018, 6106 }, { 8, DAM_DEAD, -1, 0, 0, 6107, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 6100 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_AGILITY, -5, DAM_KNOCKED_DOWN, 5016, 6100 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 6100, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6103, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_AGILITY, -1, DAM_KNOCKED_DOWN, 5001, 6100 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 6100 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 6100, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6103, 5000 }, }, }, // KILL_TYPE_MANTIS { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5001, 6200 }, { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5016, 6200 }, { 5, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -1, DAM_KNOCKED_OUT, 6200, 6201 }, { 6, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6200, 6201 }, { 6, DAM_DEAD, -1, 0, 0, 6202, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5001, 6203 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5001, 6203 }, { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5001, 6203 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5016, 6203 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5016, 6203 }, { 4, DAM_CRIP_ARM_LEFT | DAM_LOSE_TURN, -1, 0, 0, 6204, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5001, 6203 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5001, 6203 }, { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5001, 6203 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5016, 6203 }, { 4, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5016, 6203 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_LOSE_TURN, -1, 0, 0, 6204, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 1000, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_BYPASS, 5001, 6205 }, { 3, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5001, 6205 }, { 4, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5016, 6205 }, { 4, 0, STAT_ENDURANCE, -4, DAM_BYPASS, 5016, 6205 }, { 6, DAM_DEAD, -1, 0, 0, 6206, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 6201 }, { 3, 0, STAT_AGILITY, -2, DAM_KNOCKED_DOWN, 5001, 6201 }, { 4, 0, STAT_AGILITY, -4, DAM_KNOCKED_DOWN, 5001, 6201 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6201, 6203 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6201, 6203 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 6207, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 6201 }, { 3, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5001, 6201 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_LEFT, 6201, 6208 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_LEFT, 6201, 6208 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -5, DAM_CRIP_LEG_LEFT, 6201, 6208 }, { 3, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 6208, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_LOSE_TURN, 6205, 6209 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 6205, 6209 }, { 6, DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_BLIND, 6209, 6210 }, { 8, DAM_KNOCKED_DOWN | DAM_BYPASS | DAM_LOSE_TURN, STAT_ENDURANCE, -3, DAM_BLIND, 6209, 6210 }, { 8, DAM_DEAD, -1, 0, 0, 6202, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6205, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6209, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 1000, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_BYPASS, 5001, 6205 }, { 3, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5001, 6205 }, { 4, 0, STAT_ENDURANCE, -2, DAM_BYPASS, 5016, 6205 }, { 4, 0, STAT_ENDURANCE, -4, DAM_BYPASS, 5016, 6205 }, { 6, DAM_DEAD, -1, 0, 0, 6206, 5000 }, }, }, // KILL_TYPE_DEATH_CLAW { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 5016, 5023 }, { 5, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 5016, 5023 }, { 5, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN, 5016, 5023 }, { 6, 0, STAT_ENDURANCE, -4, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5004 }, { 6, 0, STAT_ENDURANCE, -5, DAM_KNOCKED_DOWN | DAM_LOSE_TURN, 5016, 5004 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 5001, 5011 }, { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_LEFT, 5001, 5011 }, { 3, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_LEFT, 5001, 5011 }, { 3, 0, STAT_ENDURANCE, -6, DAM_CRIP_ARM_LEFT, 5001, 5011 }, { 3, 0, STAT_ENDURANCE, -8, DAM_CRIP_ARM_LEFT, 5001, 5011 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 5001, 5014 }, { 3, 0, STAT_ENDURANCE, -2, DAM_CRIP_ARM_RIGHT, 5001, 5014 }, { 3, 0, STAT_ENDURANCE, -4, DAM_CRIP_ARM_RIGHT, 5001, 5014 }, { 3, 0, STAT_ENDURANCE, -6, DAM_CRIP_ARM_RIGHT, 5001, 5014 }, { 3, 0, STAT_ENDURANCE, -8, DAM_CRIP_ARM_RIGHT, 5001, 5014 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5001, 6300 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5016, 6300 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5005, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5004 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5001, 5004 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_RIGHT, 5001, 5004 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -4, DAM_CRIP_LEG_RIGHT, 5016, 5022 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -5, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -6, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5001, 5004 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 5001, 5004 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -2, DAM_CRIP_LEG_RIGHT, 5001, 5004 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -4, DAM_CRIP_LEG_RIGHT, 5016, 5022 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -5, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -6, DAM_CRIP_LEG_RIGHT, 5023, 5024 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 5001, 6301 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -6, DAM_LOSE_TURN, 6300, 6301 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_BLIND, 6301, 6302 }, { 8, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6302, 5000 }, { 8, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6302, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, -1, 0, 0, 5001, 5000 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 5, 0, STAT_AGILITY, 0, DAM_KNOCKED_DOWN, 5016, 5004 }, { 5, 0, STAT_AGILITY, -3, DAM_KNOCKED_DOWN, 5016, 5004 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 5001, 5000 }, { 3, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5001, 6300 }, { 4, 0, -1, 0, 0, 5016, 5000 }, { 4, 0, STAT_ENDURANCE, -1, DAM_BYPASS, 5016, 6300 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 5004, 5000 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 5005, 5000 }, }, }, // KILL_TYPE_PLANT { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 6405, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6400, 5000 }, { 5, 0, -1, 0, 0, 6401, 5000 }, { 5, DAM_BYPASS, -1, 0, 0, 6402, 5000 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 6402, 6403 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -6, DAM_LOSE_TURN, 6402, 6403 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6400, 5000 }, { 4, 0, -1, 0, 0, 6401, 5000 }, { 4, 0, -1, 0, 0, 6401, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, -1, 0, 0, 6405, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6400, 5000 }, { 5, 0, -1, 0, 0, 6401, 5000 }, { 5, DAM_BYPASS, -1, 0, 0, 6402, 5000 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -4, DAM_BLIND, 6402, 6406 }, { 6, DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6406, 6404 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_LOSE_TURN, 6402, 6403 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -6, DAM_LOSE_TURN, 6402, 6403 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, 0, -1, 0, 0, 6405, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6400, 5000 }, { 4, 0, -1, 0, 0, 6401, 5000 }, { 4, 0, -1, 0, 0, 6401, 5000 }, { 4, DAM_BYPASS, -1, 0, 0, 6402, 5000 }, }, }, // KILL_TYPE_GECKO { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 6701, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6700, 5003 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6700, 5003 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6700, 5003 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 6700, 5006 }, { 6, DAM_DEAD, -1, 0, 0, 6700, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 6702, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 6702, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6702, 5011 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6702, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6702, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6702, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 6702, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 6702, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6702, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6702, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6702, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6702, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 6701, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6701, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6704, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6704, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6704, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6704, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6705, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6705, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6705, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6705, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6705, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6705, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6705, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6705, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 6705, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6705, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6705, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6705, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 4, DAM_BLIND, 6700, 5028 }, { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6700, 5028 }, { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6700, 5028 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6700, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6700, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 6700, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 6703, 5000 }, { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 6703, 5035 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6703, 5036 }, { 3, DAM_KNOCKED_OUT, -1, 0, 0, 6703, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6703, 5036 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6703, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 6700, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6700, 5000 }, { 4, 0, -1, 0, 0, 6700, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6700, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6700, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6700, 5000 }, }, }, // KILL_TYPE_ALIEN { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 6801, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6800, 5003 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6800, 5003 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6803, 5003 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 6804, 5006 }, { 6, DAM_DEAD, -1, 0, 0, 6804, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 6806, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 6806, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6806, 5011 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6806, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6806, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6806, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 6806, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 6806, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6806, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6806, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6806, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6806, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 6800, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6800, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6805, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6805, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6805, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6805, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6805, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6805, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6805, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6805, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 6805, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6805, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6805, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6805, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 4, DAM_BLIND, 6803, 5028 }, { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6803, 5028 }, { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6803, 5028 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6803, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6803, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 6804, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 6801, 5000 }, { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 6801, 5035 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6801, 5036 }, { 3, DAM_KNOCKED_OUT, -1, 0, 0, 6801, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6804, 5036 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6804, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 6800, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 4, 0, -1, 0, 0, 6800, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6800, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6800, 5000 }, }, }, // KILL_TYPE_GIANT_ANT { // HIT_LOCATION_HEAD { { 4, 0, -1, 0, 0, 6901, 5000 }, { 4, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6901, 5003 }, { 5, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6902, 5003 }, { 5, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6902, 5003 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 0, DAM_BLIND, 6902, 5006 }, { 6, DAM_DEAD, -1, 0, 0, 6902, 5000 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 6906, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 6906, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_LEFT, 6906, 5011 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6906, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6906, 5000 }, { 4, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6906, 5000 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 6906, 5000 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 6906, 5000 }, { 4, 0, STAT_ENDURANCE, -3, DAM_CRIP_ARM_RIGHT, 6906, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6906, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6906, 5000 }, { 4, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6906, 5000 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 6900, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6900, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6904, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6904, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6904, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6904, 5000 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6905, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6905, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 6905, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6905, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6905, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6905, 5000 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6905, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6905, 5024 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 6905, 5024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6905, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6905, 5026 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6905, 5000 }, }, // HIT_LOCATION_EYES { { 4, 0, STAT_LUCK, 4, DAM_BLIND, 6900, 5028 }, { 4, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6906, 5028 }, { 6, DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6901, 5028 }, { 6, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6901, 5000 }, { 8, DAM_KNOCKED_OUT | DAM_BLIND | DAM_BYPASS, -1, 0, 0, 6901, 5000 }, { 8, DAM_DEAD, -1, 0, 0, 6901, 5000 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 6900, 5000 }, { 3, DAM_BYPASS, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 6900, 5035 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_KNOCKED_OUT, 6900, 5036 }, { 3, DAM_KNOCKED_OUT, -1, 0, 0, 6903, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_OUT, 6903, 5036 }, { 4, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6903, 5000 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 6900, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6900, 5000 }, { 4, 0, -1, 0, 0, 6904, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6904, 5000 }, { 6, DAM_KNOCKED_OUT | DAM_BYPASS, -1, 0, 0, 6904, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6904, 5000 }, }, }, // KILL_TYPE_BIG_BAD_BOSS { // HIT_LOCATION_HEAD { { 3, 0, -1, 0, 0, 7101, 7100 }, { 3, 0, -1, 0, 0, 7102, 7103 }, { 4, 0, -1, 0, 0, 7102, 7103 }, { 4, DAM_LOSE_TURN, -1, 0, 0, 7104, 7103 }, { 5, DAM_KNOCKED_DOWN, STAT_LUCK, 0, DAM_BLIND, 7105, 7106 }, { 6, DAM_KNOCKED_DOWN, -1, 0, 0, 7105, 7100 }, }, // HIT_LOCATION_LEFT_ARM { { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 7106, 7011 }, { 4, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 7106, 7100 }, { 4, DAM_CRIP_ARM_LEFT, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_RIGHT_ARM { { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 }, { 4, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 7106, 7100 }, { 4, DAM_CRIP_ARM_RIGHT, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_TORSO { { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, 0, -1, 0, 0, 7106, 7100 }, { 4, 0, -1, 0, 0, 7106, 7100 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_RIGHT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 7106, 7106 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_RIGHT, 7060, 7106 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 7106, 7100 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT, -1, 0, 0, 7106, 7106 }, { 4, DAM_CRIP_LEG_RIGHT, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_LEFT_LEG { { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 7106, 7024 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, -3, DAM_CRIP_LEG_LEFT, 7106, 7024 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 7106, 7100 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT, -1, 0, 0, 7106, 7106 }, { 4, DAM_CRIP_LEG_LEFT, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_EYES { { 3, 0, -1, 0, 0, 7106, 7106 }, { 3, 0, -1, 0, 0, 7106, 7106 }, { 4, 0, STAT_LUCK, 2, DAM_BLIND, 7106, 7106 }, { 4, DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 }, { 5, DAM_BLIND | DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 }, { 5, DAM_BLIND | DAM_LOSE_TURN, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_GROIN { { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, 0, STAT_ENDURANCE, -3, DAM_KNOCKED_DOWN, 7106, 7106 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7106 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, { 4, 0, -1, 0, 0, 7106, 7106 }, { 4, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, }, // HIT_LOCATION_UNCALLED { { 3, 0, -1, 0, 0, 7106, 7100 }, { 3, 0, -1, 0, 0, 7106, 7100 }, { 4, 0, -1, 0, 0, 7106, 7100 }, { 4, 0, -1, 0, 0, 7106, 7100 }, { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, { 5, DAM_KNOCKED_DOWN, -1, 0, 0, 7106, 7100 }, }, }, }; // Player's criticals effects. // // 0x5179B0 static CriticalHitDescription pc_crit_succ_eff[HIT_LOCATION_COUNT][CRTICIAL_EFFECT_COUNT] = { { { 3, 0, -1, 0, 0, 6500, 5000 }, { 3, DAM_BYPASS, STAT_ENDURANCE, 3, DAM_KNOCKED_DOWN, 6501, 6503 }, { 3, DAM_BYPASS, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6501, 6503 }, { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_ENDURANCE, 2, DAM_KNOCKED_OUT, 6503, 6502 }, { 3, DAM_KNOCKED_OUT | DAM_BYPASS, STAT_LUCK, 2, DAM_BLIND, 6502, 6504 }, { 6, DAM_BYPASS, STAT_ENDURANCE, -2, DAM_DEAD, 6501, 6505 }, }, { { 2, 0, -1, 0, 0, 6506, 5000 }, { 2, DAM_LOSE_TURN, -1, 0, 0, 6507, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_LEFT, 6508, 6509 }, { 3, DAM_BYPASS, -1, 0, 0, 6501, 5000 }, { 3, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6510, 5000 }, { 3, DAM_CRIP_ARM_LEFT | DAM_BYPASS, -1, 0, 0, 6510, 5000 }, }, { { 2, 0, -1, 0, 0, 6506, 5000 }, { 2, DAM_LOSE_TURN, -1, 0, 0, 6507, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_CRIP_ARM_RIGHT, 6508, 6509 }, { 3, DAM_BYPASS, -1, 0, 0, 6501, 5000 }, { 3, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6511, 5000 }, { 3, DAM_CRIP_ARM_RIGHT | DAM_BYPASS, -1, 0, 0, 6511, 5000 }, }, { { 3, 0, -1, 0, 0, 6512, 5000 }, { 3, 0, -1, 0, 0, 6512, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6508, 5000 }, { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_LUCK, 2, DAM_DEAD, 6503, 6513 }, }, { { 3, 0, -1, 0, 0, 6512, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6514, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_RIGHT, 6514, 6515 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6516, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6516, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_RIGHT | DAM_BYPASS, -1, 0, 0, 6517, 5000 }, }, { { 3, 0, -1, 0, 0, 6512, 5000 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6514, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 0, DAM_CRIP_LEG_LEFT, 6514, 6515 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6516, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6516, 5000 }, { 4, DAM_KNOCKED_OUT | DAM_CRIP_LEG_LEFT | DAM_BYPASS, -1, 0, 0, 6517, 5000 }, }, { { 3, 0, -1, 0, 0, 6518, 5000 }, { 3, 0, STAT_LUCK, 3, DAM_BLIND, 6518, 6519 }, { 3, DAM_BYPASS, STAT_LUCK, 3, DAM_BLIND, 6501, 6519 }, { 4, DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6520, 5000 }, { 4, DAM_BLIND | DAM_BYPASS | DAM_LOSE_TURN, -1, 0, 0, 6521, 5000 }, { 6, DAM_DEAD, -1, 0, 0, 6522, 5000 }, }, { { 3, 0, -1, 0, 0, 6523, 5000 }, { 3, 0, STAT_ENDURANCE, 0, DAM_KNOCKED_DOWN, 6523, 6524 }, { 3, DAM_KNOCKED_DOWN, -1, 0, 0, 6524, 5000 }, { 3, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 4, DAM_KNOCKED_OUT, 6524, 6525 }, { 4, DAM_KNOCKED_DOWN, STAT_ENDURANCE, 2, DAM_KNOCKED_OUT, 6524, 6525 }, { 4, DAM_KNOCKED_OUT, -1, 0, 0, 6526, 5000 }, }, { { 3, 0, -1, 0, 0, 6512, 5000 }, { 3, 0, -1, 0, 0, 6512, 5000 }, { 3, DAM_BYPASS, -1, 0, 0, 6508, 5000 }, { 3, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, -1, 0, 0, 6503, 5000 }, { 4, DAM_KNOCKED_DOWN | DAM_BYPASS, STAT_LUCK, 2, DAM_DEAD, 6503, 6513 }, }, }; // 0x517F98 static int combat_end_due_to_load = 0; // 0x517F9C static bool combat_cleanup_enabled = false; // Provides effects caused by failing weapons. // // 0x517FA0 int cf_table[WEAPON_CRITICAL_FAILURE_TYPE_COUNT][WEAPON_CRITICAL_FAILURE_EFFECT_COUNT] = { { 0, DAM_LOSE_TURN, DAM_LOSE_TURN, DAM_HURT_SELF | DAM_KNOCKED_DOWN, DAM_CRIP_RANDOM }, { 0, DAM_LOSE_TURN, DAM_DROP, DAM_RANDOM_HIT, DAM_HIT_SELF }, { 0, DAM_LOSE_AMMO, DAM_DROP, DAM_RANDOM_HIT, DAM_DESTROY }, { DAM_LOSE_TURN, DAM_LOSE_TURN | DAM_LOSE_AMMO, DAM_DROP | DAM_LOSE_TURN, DAM_RANDOM_HIT, DAM_EXPLODE | DAM_LOSE_TURN }, { DAM_DUD, DAM_DROP, DAM_DROP | DAM_HURT_SELF, DAM_RANDOM_HIT, DAM_EXPLODE }, { DAM_LOSE_TURN, DAM_DUD, DAM_DESTROY, DAM_RANDOM_HIT, DAM_EXPLODE | DAM_LOSE_TURN | DAM_KNOCKED_DOWN }, { 0, DAM_LOSE_TURN, DAM_RANDOM_HIT, DAM_DESTROY, DAM_EXPLODE | DAM_LOSE_TURN | DAM_ON_FIRE }, }; // 0x51802C static int call_ty[4] = { 122, 188, 251, 316, }; // 0x51803C static int hit_loc_left[4] = { HIT_LOCATION_HEAD, HIT_LOCATION_EYES, HIT_LOCATION_RIGHT_ARM, HIT_LOCATION_RIGHT_LEG, }; // 0x51804C static int hit_loc_right[4] = { HIT_LOCATION_TORSO, HIT_LOCATION_GROIN, HIT_LOCATION_LEFT_ARM, HIT_LOCATION_LEFT_LEG, }; // 0x56D2B0 static Attack main_ctd; // combat.msg // // 0x56D368 MessageList combat_message_file; // 0x56D370 static Object* call_target; // 0x56D374 static int call_win; // 0x56D378 static int combat_elev; // 0x56D37C static int list_total; // Probably last who_hit_me of obj_dude // // 0x56D380 static Object* combat_ending_guy; // 0x56D384 static int list_noncom; // 0x56D388 Object* combat_turn_obj; // target_highlight // // 0x56D38C static int combat_highlight; // 0x56D390 static Object** combat_list; // 0x56D394 static int list_com; // Experience received for killing critters during current combat. // // 0x56D398 int combat_exps; // bonus action points from BONUS_MOVE perk. // // 0x56D39C int combat_free_move; // combat_init // 0x420CC0 int combat_init() { int max_action_points; char path[MAX_PATH]; combat_turn_running = 0; combatNumTurns = 0; combat_list = 0; aiInfoList = 0; list_com = 0; list_noncom = 0; list_total = 0; gcsd = 0; combat_call_display = 0; combat_state = COMBAT_STATE_0x02; max_action_points = critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS); combat_free_move = 0; combat_ending_guy = NULL; combat_end_due_to_load = 0; obj_dude->data.critter.combat.ap = max_action_points; combat_cleanup_enabled = 0; if (!message_init(&combat_message_file)) { return -1; } sprintf(path, "%s%s", msg_path, "combat.msg"); if (!message_load(&combat_message_file, path)) { return -1; } return 0; } // 0x420DA0 void combat_reset() { int max_action_points; combat_turn_running = 0; combatNumTurns = 0; combat_list = 0; aiInfoList = 0; list_com = 0; list_noncom = 0; list_total = 0; gcsd = 0; combat_call_display = 0; combat_state = COMBAT_STATE_0x02; max_action_points = critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS); combat_free_move = 0; combat_ending_guy = NULL; obj_dude->data.critter.combat.ap = max_action_points; } // 0x420E14 void combat_exit() { message_exit(&combat_message_file); } // 0x420E24 int find_cid(int a1, int cid, Object** critterList, int critterListLength) { int index; for (index = a1; index < critterListLength; index++) { if (critterList[index]->cid == cid) { break; } } return index; } // 0x420E4C int combat_load(File* stream) { int v14; int a2; Object* obj; int v24; int i; int j; if (db_freadInt(stream, &combat_state) == -1) return -1; if (!isInCombat()) { obj = obj_find_first(); while (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { if (obj->data.critter.combat.whoHitMeCid == -1) { obj->data.critter.combat.whoHitMe = NULL; } } obj = obj_find_next(); } return 0; } if (db_freadInt(stream, &combat_turn_running) == -1) return -1; if (db_freadInt(stream, &combat_free_move) == -1) return -1; if (db_freadInt(stream, &combat_exps) == -1) return -1; if (db_freadInt(stream, &list_com) == -1) return -1; if (db_freadInt(stream, &list_noncom) == -1) return -1; if (db_freadInt(stream, &list_total) == -1) return -1; if (obj_create_list(-1, map_elevation, 1, &combat_list) != list_total) { obj_delete_list(combat_list); return -1; } if (db_freadInt(stream, &v24) == -1) return -1; obj_dude->cid = v24; for (i = 0; i < list_total; i++) { if (combat_list[i]->data.critter.combat.whoHitMeCid == -1) { combat_list[i]->data.critter.combat.whoHitMe = NULL; } else { for (j = 0; j < list_total; j++) { if (combat_list[i]->data.critter.combat.whoHitMeCid == combat_list[j]->cid) { break; } } if (j == list_total) { combat_list[i]->data.critter.combat.whoHitMe = NULL; } else { combat_list[i]->data.critter.combat.whoHitMe = combat_list[j]; } } } for (i = 0; i < list_total; i++) { if (db_freadInt(stream, &v24) == -1) return -1; for (j = i; j < list_total; j++) { if (v24 == combat_list[j]->cid) { break; } } if (j == list_total) { return -1; } obj = combat_list[i]; combat_list[i] = combat_list[j]; combat_list[j] = obj; } for (i = 0; i < list_total; i++) { combat_list[i]->cid = i; } if (aiInfoList) { mem_free(aiInfoList); } aiInfoList = (CombatAiInfo*)mem_malloc(sizeof(*aiInfoList) * list_total); if (aiInfoList == NULL) { return -1; } for (v14 = 0; v14 < list_total; v14++) { CombatAiInfo* aiInfo = &(aiInfoList[v14]); if (db_freadInt(stream, &a2) == -1) return -1; if (a2 == -1) { aiInfo->friendlyDead = NULL; } else { aiInfo->friendlyDead = objFindObjPtrFromID(a2); if (aiInfo->friendlyDead == NULL) return -1; } if (db_freadInt(stream, &a2) == -1) return -1; if (a2 == -1) { aiInfo->lastTarget = NULL; } else { aiInfo->lastTarget = objFindObjPtrFromID(a2); if (aiInfo->lastTarget == NULL) return -1; } if (db_freadInt(stream, &a2) == -1) return -1; if (a2 == -1) { aiInfo->lastItem = NULL; } else { aiInfo->lastItem = objFindObjPtrFromID(a2); if (aiInfo->lastItem == NULL) return -1; } if (db_freadInt(stream, &(aiInfo->lastMove)) == -1) return -1; } combat_begin_extra(obj_dude); return 0; } // 0x421244 int combat_save(File* stream) { if (db_fwriteInt(stream, combat_state) == -1) return -1; if (!isInCombat()) return 0; if (db_fwriteInt(stream, combat_turn_running) == -1) return -1; if (db_fwriteInt(stream, combat_free_move) == -1) return -1; if (db_fwriteInt(stream, combat_exps) == -1) return -1; if (db_fwriteInt(stream, list_com) == -1) return -1; if (db_fwriteInt(stream, list_noncom) == -1) return -1; if (db_fwriteInt(stream, list_total) == -1) return -1; if (db_fwriteInt(stream, obj_dude->cid) == -1) return -1; for (int index = 0; index < list_total; index++) { if (db_fwriteInt(stream, combat_list[index]->cid) == -1) return -1; } if (aiInfoList == NULL) { return -1; } for (int index = 0; index < list_total; index++) { CombatAiInfo* aiInfo = &(aiInfoList[index]); if (db_fwriteInt(stream, aiInfo->friendlyDead != NULL ? aiInfo->friendlyDead->id : -1) == -1) return -1; if (db_fwriteInt(stream, aiInfo->lastTarget != NULL ? aiInfo->lastTarget->id : -1) == -1) return -1; if (db_fwriteInt(stream, aiInfo->lastItem != NULL ? aiInfo->lastItem->id : -1) == -1) return -1; if (db_fwriteInt(stream, aiInfo->lastMove) == -1) return -1; } return 0; } // 0x4213E8 bool combat_safety_invalidate_weapon(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr) { return combat_safety_invalidate_weapon_func(attacker, weapon, hitMode, defender, safeDistancePtr, NULL); } // 0x4213FC bool combat_safety_invalidate_weapon_func(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr, Object* attackerFriend) { if (safeDistancePtr != NULL) { *safeDistancePtr = 0; } if (attacker->pid == PROTO_ID_0x10001E0) { return false; } int intelligence = critterGetStat(attacker, STAT_INTELLIGENCE); int team = attacker->data.critter.combat.team; int damageRadius = item_w_area_damage_radius(weapon, hitMode); int maxDamage; item_w_damage_min_max(weapon, NULL, &maxDamage); int damageType = item_w_damage_type(attacker, weapon); if (damageRadius > 0) { if (intelligence < 5) { damageRadius -= 5 - intelligence; if (damageRadius < 0) { damageRadius = 0; } } if (attackerFriend != NULL) { if (obj_dist(defender, attackerFriend) < damageRadius) { debug_printf("Friendly was in the way!"); return true; } } for (int index = 0; index < list_total; index++) { Object* candidate = combat_list[index]; if (candidate->data.critter.combat.team == team && candidate != attacker && candidate != defender && !critter_is_dead(candidate)) { int attackerDefenderDistance = obj_dist(defender, candidate); if (attackerDefenderDistance < damageRadius && candidate != candidate->data.critter.combat.whoHitMe) { int damageThreshold = critterGetStat(candidate, STAT_DAMAGE_THRESHOLD + damageType); int damageResistance = critterGetStat(candidate, STAT_DAMAGE_RESISTANCE + damageType); if (damageResistance * (maxDamage - damageThreshold) / 100 > 0) { return true; } } } } if (obj_dist(defender, attacker) <= damageRadius) { if (safeDistancePtr != NULL) { *safeDistancePtr = damageRadius - obj_dist(defender, attacker) + 1; return false; } return true; } return false; } int anim = item_w_anim_weap(weapon, hitMode); if (anim != ANIM_FIRE_BURST && anim != ANIM_FIRE_CONTINUOUS) { return false; } Attack attack; combat_ctd_init(&attack, attacker, defender, hitMode, HIT_LOCATION_TORSO); int accuracy = determine_to_hit_func(attacker, attacker->tile, defender, HIT_LOCATION_TORSO, hitMode, 1); int roundsHitMainTarget; int roundsSpent; compute_spray(&attack, accuracy, &roundsHitMainTarget, &roundsSpent, anim); if (attackerFriend != NULL) { for (int index = 0; index < attack.extrasLength; index++) { if (attack.extras[index] == attackerFriend) { debug_printf("Friendly was in the way!"); return true; } } } for (int index = 0; index < attack.extrasLength; index++) { Object* candidate = attack.extras[index]; if (candidate->data.critter.combat.team == team && candidate != attacker && candidate != defender && !critter_is_dead(candidate) && candidate != candidate->data.critter.combat.whoHitMe) { int damageThreshold = critterGetStat(candidate, STAT_DAMAGE_THRESHOLD + damageType); int damageResistance = critterGetStat(candidate, STAT_DAMAGE_RESISTANCE + damageType); if (damageResistance * (maxDamage - damageThreshold) / 100 > 0) { return true; } } } return false; } // 0x4217BC bool combatTestIncidentalHit(Object* attacker, Object* defender, Object* attackerFriend, Object* weapon) { return combat_safety_invalidate_weapon_func(attacker, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL, attackerFriend); } // 0x4217D4 Object* combat_whose_turn() { if (isInCombat()) { return combat_turn_obj; } else { return NULL; } } // 0x4217E8 void combat_data_init(Object* obj) { obj->data.critter.combat.damageLastTurn = 0; obj->data.critter.combat.results = 0; } // NOTE: Inlined. // // 0x4217FC static void combatInitAIInfoList() { int index; for (index = 0; index < list_total; index++) { aiInfoList[index].friendlyDead = NULL; aiInfoList[index].lastTarget = NULL; aiInfoList[index].lastItem = NULL; aiInfoList[index].lastMove = 0; } } // 0x421850 static int combatCopyAIInfo(int srcIndex, int destIndex) { CombatAiInfo* src = &aiInfoList[srcIndex]; CombatAiInfo* dest = &aiInfoList[destIndex]; dest->friendlyDead = src->friendlyDead; dest->lastTarget = src->lastTarget; dest->lastItem = src->lastItem; dest->lastMove = src->lastMove; return 0; } // 0x421880 Object* combatAIInfoGetFriendlyDead(Object* obj) { if (!isInCombat()) { return NULL; } if (obj == NULL) { return NULL; } if (obj->cid == -1) { return NULL; } return aiInfoList[obj->cid].friendlyDead; } // 0x4218AC int combatAIInfoSetFriendlyDead(Object* a1, Object* a2) { if (!isInCombat()) { return 0; } if (a1 == NULL) { return -1; } if (a1->cid == -1) { return -1; } if (a1 == a2) { return -1; } aiInfoList[a1->cid].friendlyDead = a2; return 0; } // 0x4218EC Object* combatAIInfoGetLastTarget(Object* obj) { if (!isInCombat()) { return NULL; } if (obj == NULL) { return NULL; } if (obj->cid == -1) { return NULL; } return aiInfoList[obj->cid].lastTarget; } // 0x421918 int combatAIInfoSetLastTarget(Object* a1, Object* a2) { if (!isInCombat()) { return 0; } if (a1 == NULL) { return -1; } if (a1->cid == -1) { return -1; } if (a1 == a2) { return -1; } if (critter_is_dead(a2)) { a2 = NULL; } aiInfoList[a1->cid].lastTarget = a2; return 0; } // 0x42196C Object* combatAIInfoGetLastItem(Object* obj) { int v1; if (!isInCombat()) { return NULL; } if (obj == NULL) { return NULL; } v1 = obj->cid; if (v1 == -1) { return NULL; } return aiInfoList[v1].lastItem; } // 0x421998 int combatAIInfoSetLastItem(Object* obj, Object* a2) { int v2; if (!isInCombat()) { return 0; } if (obj == NULL) { return -1; } v2 = obj->cid; if (v2 == -1) { return -1; } aiInfoList[v2].lastItem = NULL; return 0; } // NOTE: Unused. // // 0x4219CC int combatAIInfoGetLastMove(Object* object) { if (!isInCombat()) { return 0; } if (object == NULL) { return -1; } if (object->cid == -1) { return -1; } return aiInfoList[object->cid].lastMove; } // NOTE: Inlined. // // 0x421A00 int combatAIInfoSetLastMove(Object* object, int move) { if (!isInCombat()) { return 0; } if (object == NULL) { return -1; } if (object->cid == -1) { return -1; } aiInfoList[object->cid].lastMove = move; return 0; } // 0x421A34 static void combat_begin(Object* a1) { combat_turn_running = 0; anim_stop(); remove_bk_process(dude_fidget); combat_elev = map_elevation; if (!isInCombat()) { combatNumTurns = 0; combat_exps = 0; combat_list = NULL; list_total = obj_create_list(-1, combat_elev, OBJ_TYPE_CRITTER, &combat_list); list_noncom = list_total; list_com = 0; aiInfoList = (CombatAiInfo*)mem_malloc(sizeof(*aiInfoList) * list_total); if (aiInfoList == NULL) { return; } // NOTE: Uninline. combatInitAIInfoList(); Object* v1 = NULL; for (int index = 0; index < list_total; index++) { Object* critter = combat_list[index]; CritterCombatData* combatData = &(critter->data.critter.combat); combatData->maneuver &= CRITTER_MANEUVER_0x01; combatData->damageLastTurn = 0; combatData->whoHitMe = NULL; combatData->ap = 0; critter->cid = index; // NOTE: Uninline. combatAIInfoSetLastMove(critter, 0); scr_set_objs(critter->sid, NULL, NULL); scr_set_ext_param(critter->sid, 0); if (critter->pid == 0x1000098) { if (!critter_is_dead(critter)) { v1 = critter; } } } combat_state |= COMBAT_STATE_0x01; tile_refresh_display(); game_ui_disable(0); gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH); combat_ending_guy = NULL; combat_begin_extra(a1); caiTeamCombatInit(combat_list, list_total); intface_end_window_open(true); gmouse_enable_scrolling(); if (v1 != NULL && !isLoadingGame()) { int fid = art_id(FID_TYPE(v1->fid), 100, FID_ANIM_TYPE(v1->fid), (v1->fid & 0xF000) >> 12, (v1->fid & 0x70000000) >> 28); register_clear(v1); register_begin(ANIMATION_REQUEST_RESERVED); register_object_animate(v1, ANIM_UP_STAIRS_RIGHT, -1); register_object_change_fid(v1, fid, -1); register_end(); while (anim_busy(v1)) { process_bk(); } } } } // 0x421C8C static void combat_begin_extra(Object* a1) { for (int index = 0; index < list_total; index++) { combat_update_critter_outline_for_los(combat_list[index], 0); } combat_ctd_init(&main_ctd, a1, NULL, 4, 3); combat_turn_obj = a1; combat_ai_begin(list_total, combat_list); combat_highlight = 2; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &combat_highlight); } // NOTE: Inlined. // // 0x421D18 static void combat_update_critters_in_los(int a1) { int index; for (index = 0; index < list_total; index++) { combat_update_critter_outline_for_los(combat_list[index], a1); } } // Something with outlining. // // 0x421D50 void combat_update_critter_outline_for_los(Object* critter, bool a2) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return; } if (critter == obj_dude) { return; } if (critter_is_dead(critter)) { return; } bool v5 = false; if (!combat_is_shot_blocked(obj_dude, obj_dude->tile, critter->tile, critter, 0)) { v5 = true; } if (v5) { int outlineType = critter->outline & OUTLINE_TYPE_MASK; if (outlineType != OUTLINE_TYPE_HOSTILE && outlineType != OUTLINE_TYPE_FRIENDLY) { int newOutlineType = obj_dude->data.critter.combat.team == critter->data.critter.combat.team ? OUTLINE_TYPE_FRIENDLY : OUTLINE_TYPE_HOSTILE; obj_turn_off_outline(critter, NULL); obj_remove_outline(critter, NULL); obj_outline_object(critter, newOutlineType, NULL); if (a2) { obj_turn_on_outline(critter, NULL); } else { obj_turn_off_outline(critter, NULL); } } else { if (critter->outline != 0 && (critter->outline & OUTLINE_DISABLED) == 0) { if (!a2) { obj_turn_off_outline(critter, NULL); } } else { if (a2) { obj_turn_on_outline(critter, NULL); } } } } else { int v7 = obj_dist(obj_dude, critter); int v8 = critterGetStat(obj_dude, STAT_PERCEPTION) * 5; if ((critter->flags & OBJECT_TRANS_GLASS) != 0) { v8 /= 2; } if (v7 <= v8) { v5 = true; } int outlineType = critter->outline & OUTLINE_TYPE_MASK; if (outlineType != OUTLINE_TYPE_32) { obj_turn_off_outline(critter, NULL); obj_remove_outline(critter, NULL); if (v5) { obj_outline_object(critter, OUTLINE_TYPE_32, NULL); if (a2) { obj_turn_on_outline(critter, NULL); } else { obj_turn_off_outline(critter, NULL); } } } else { if (critter->outline != 0 && (critter->outline & OUTLINE_DISABLED) == 0) { if (!a2) { obj_turn_off_outline(critter, NULL); } } else { if (a2) { obj_turn_on_outline(critter, NULL); } } } } } // Probably complete combat sequence. // // 0x421EFC static void combat_over() { if (game_user_wants_to_quit == 0) { for (int index = 0; index < list_com; index++) { Object* critter = combat_list[index]; if (critter != obj_dude) { cai_attempt_w_reload(critter, 0); } } } add_bk_process(dude_fidget); for (int index = 0; index < list_noncom + list_com; index++) { Object* critter = combat_list[index]; critter->data.critter.combat.damageLastTurn = 0; critter->data.critter.combat.maneuver = CRITTER_MANEUVER_NONE; } for (int index = 0; index < list_total; index++) { Object* critter = combat_list[index]; critter->data.critter.combat.ap = 0; obj_remove_outline(critter, NULL); critter->data.critter.combat.whoHitMe = NULL; scr_set_objs(critter->sid, NULL, NULL); scr_set_ext_param(critter->sid, 0); if (critter->pid == 0x1000098 && !critter_is_dead(critter) && !isLoadingGame()) { int fid = art_id(FID_TYPE(critter->fid), 99, FID_ANIM_TYPE(critter->fid), (critter->fid & 0xF000) >> 12, (critter->fid & 0x70000000) >> 28); register_clear(critter); register_begin(ANIMATION_REQUEST_RESERVED); register_object_animate(critter, ANIM_UP_STAIRS_RIGHT, -1); register_object_change_fid(critter, fid, -1); register_end(); while (anim_busy(critter)) { process_bk(); } } } tile_refresh_display(); int leftItemAction; int rightItemAction; intface_get_item_states(&leftItemAction, &rightItemAction); intface_update_items(true, leftItemAction, rightItemAction); obj_dude->data.critter.combat.ap = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); intface_update_move_points(0, 0); if (game_user_wants_to_quit == 0) { combat_give_exps(combat_exps); } combat_exps = 0; combat_state &= ~(COMBAT_STATE_0x01 | COMBAT_STATE_0x02); combat_state |= COMBAT_STATE_0x02; if (list_total != 0) { obj_delete_list(combat_list); if (aiInfoList != NULL) { mem_free(aiInfoList); } aiInfoList = NULL; } list_total = 0; combat_ai_over(); game_ui_enable(); gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); intface_update_ac(true); if (critter_is_prone(obj_dude) && !critter_is_dead(obj_dude) && combat_ending_guy == NULL) { queue_remove_this(obj_dude, EVENT_TYPE_KNOCKOUT); critter_wake_up(obj_dude, NULL); } } // 0x422194 void combat_over_from_load() { combat_over(); combat_state = 0; combat_end_due_to_load = 1; } // Give exp for destroying critter. // // 0x4221B4 void combat_give_exps(int exp_points) { MessageListItem v7; MessageListItem v9; int current_hp; int max_hp; char text[132]; if (exp_points <= 0) { return; } if (critter_is_dead(obj_dude)) { return; } stat_pc_add_experience(exp_points); v7.num = 621; // %s you earn %d exp. points. if (!message_search(&proto_main_msg_file, &v7)) { return; } v9.num = roll_random(0, 3) + 622; // generate prefix for message current_hp = critterGetStat(obj_dude, STAT_CURRENT_HIT_POINTS); max_hp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); if (current_hp == max_hp && roll_random(0, 100) > 65) { v9.num = 626; // Best possible prefix: For destroying your enemies without taking a scratch, } if (!message_search(&proto_main_msg_file, &v9)) { return; } sprintf(text, v7.text, v9.text, exp_points); display_print(text); } // 0x4222A8 static void combat_add_noncoms() { combatai_notify_friends(obj_dude); for (int index = list_com; index < list_com + list_noncom; index++) { Object* obj = combat_list[index]; if (combatai_want_to_join(obj)) { obj->data.critter.combat.maneuver = CRITTER_MANEUVER_NONE; Object** objectPtr1 = &(combat_list[index]); Object** objectPtr2 = &(combat_list[list_com]); Object* t = *objectPtr1; *objectPtr1 = *objectPtr2; *objectPtr2 = t; list_com += 1; list_noncom -= 1; int actionPoints = 0; if (obj != obj_dude) { actionPoints = critterGetStat(obj, STAT_MAXIMUM_ACTION_POINTS); } if (gcsd != NULL) { actionPoints += gcsd->actionPointsBonus; } obj->data.critter.combat.ap = actionPoints; combat_turn(obj, false); } } } // NOTE: Unused. // // 0x422374 int combat_in_range(Object* critter) { int perception; int index; perception = critterGetStat(critter, STAT_PERCEPTION); for (index = 0; index < list_com; index++) { if (obj_dist(combat_list[index], critter) <= perception) { return 1; } } return 0; } // Compares critters by sequence. // // 0x4223C8 static int compare_faster(const void* a1, const void* a2) { Object* v1 = *(Object**)a1; Object* v2 = *(Object**)a2; int sequence1 = critterGetStat(v1, STAT_SEQUENCE); int sequence2 = critterGetStat(v2, STAT_SEQUENCE); if (sequence1 > sequence2) { return -1; } else if (sequence1 < sequence2) { return 1; } int luck1 = critterGetStat(v1, STAT_LUCK); int luck2 = critterGetStat(v2, STAT_LUCK); if (luck1 > luck2) { return -1; } else if (luck1 < luck2) { return 1; } return 0; } // 0x42243C static void combat_sequence_init(Object* a1, Object* a2) { int next = 0; if (a1 != NULL) { for (int index = 0; index < list_total; index++) { Object* obj = combat_list[index]; if (obj == a1) { Object* temp = combat_list[next]; combat_list[index] = temp; combat_list[next] = obj; next += 1; break; } } } if (a2 != NULL) { for (int index = 0; index < list_total; index++) { Object* obj = combat_list[index]; if (obj == a2) { Object* temp = combat_list[next]; combat_list[index] = temp; combat_list[next] = obj; next += 1; break; } } } if (a1 != obj_dude && a2 != obj_dude) { for (int index = 0; index < list_total; index++) { Object* obj = combat_list[index]; if (obj == obj_dude) { Object* temp = combat_list[next]; combat_list[index] = temp; combat_list[next] = obj; next += 1; break; } } } list_com = next; list_noncom -= next; if (a1 != NULL) { critter_set_who_hit_me(a1, a2); } if (a2 != NULL) { critter_set_who_hit_me(a2, a1); } } // 0x422580 static void combat_sequence() { combat_add_noncoms(); int count = list_com; for (int index = 0; index < count; index++) { Object* critter = combat_list[index]; if ((critter->data.critter.combat.results & DAM_DEAD) != 0) { combat_list[index] = combat_list[count - 1]; combat_list[count - 1] = critter; combat_list[count - 1] = combat_list[list_noncom + count - 1]; combat_list[list_noncom + count - 1] = critter; index -= 1; count -= 1; } } for (int index = 0; index < count; index++) { Object* critter = combat_list[index]; if (critter != obj_dude) { if ((critter->data.critter.combat.results & DAM_KNOCKED_OUT) != 0 || critter->data.critter.combat.maneuver == CRITTER_MANEUVER_STOP_ATTACKING) { critter->data.critter.combat.maneuver &= ~CRITTER_MANEUVER_0x01; list_noncom += 1; combat_list[index] = combat_list[count - 1]; combat_list[count - 1] = critter; count -= 1; index -= 1; } } } if (count != 0) { list_com = count; qsort(combat_list, count, sizeof(*combat_list), compare_faster); count = list_com; } list_com = count; inc_game_time_in_seconds(5); } // 0x422694 void combat_end() { if (combat_elev == obj_dude->elevation) { MessageListItem messageListItem; int dudeTeam = obj_dude->data.critter.combat.team; for (int index = 0; index < list_com; index++) { Object* critter = combat_list[index]; if (critter != obj_dude) { int critterTeam = critter->data.critter.combat.team; Object* critterWhoHitMe = critter->data.critter.combat.whoHitMe; if (critterTeam != dudeTeam || (critterWhoHitMe != NULL && critterWhoHitMe->data.critter.combat.team == critterTeam)) { if (!combatai_want_to_stop(critter)) { messageListItem.num = 103; if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } return; } } } } for (int index = list_com; index < list_com + list_noncom; index++) { Object* critter = combat_list[index]; if (critter != obj_dude) { int critterTeam = critter->data.critter.combat.team; Object* critterWhoHitMe = critter->data.critter.combat.whoHitMe; if (critterTeam != dudeTeam || (critterWhoHitMe != NULL && critterWhoHitMe->data.critter.combat.team == critterTeam)) { if (combatai_want_to_join(critter)) { messageListItem.num = 103; if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } return; } } } } } combat_state |= COMBAT_STATE_0x08; caiTeamCombatExit(); } // 0x4227DC void combat_turn_run() { while (combat_turn_running > 0) { process_bk(); } } // 0x4227F4 static int combat_input() { while ((combat_state & COMBAT_STATE_0x02) != 0) { if ((combat_state & COMBAT_STATE_0x08) != 0) { break; } if ((obj_dude->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) { break; } if (game_user_wants_to_quit != 0) { break; } if (combat_end_due_to_load != 0) { break; } int keyCode = get_input(); if (action_explode_running()) { while (combat_turn_running > 0) { process_bk(); } } if (obj_dude->data.critter.combat.ap <= 0 && combat_free_move <= 0) { break; } if (keyCode == KEY_SPACE) { break; } if (keyCode == KEY_RETURN) { combat_end(); } else { scripts_check_state_in_combat(); game_handle_input(keyCode, true); } } int v4 = game_user_wants_to_quit; if (game_user_wants_to_quit == 1) { game_user_wants_to_quit = 0; } if ((combat_state & COMBAT_STATE_0x08) != 0) { combat_state &= ~COMBAT_STATE_0x08; return -1; } if (game_user_wants_to_quit != 0 || v4 != 0 || combat_end_due_to_load != 0) { return -1; } scripts_check_state_in_combat(); return 0; } // NOTE: Unused. // // 0x42290C void combat_end_turn() { combat_state &= ~COMBAT_STATE_0x02; } // 0x422914 static void combat_set_move_all() { for (int index = 0; index < list_com; index++) { Object* object = combat_list[index]; int actionPoints = critterGetStat(object, STAT_MAXIMUM_ACTION_POINTS); if (gcsd) { actionPoints += gcsd->actionPointsBonus; } object->data.critter.combat.ap = actionPoints; // NOTE: Uninline. combatAIInfoSetLastMove(object, 0); } } // 0x42299C static int combat_turn(Object* a1, bool a2) { combat_turn_obj = a1; combat_ctd_init(&main_ctd, a1, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); if ((a1->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) { a1->data.critter.combat.results &= ~DAM_LOSE_TURN; } else { if (a1 == obj_dude) { kb_clear(); intface_update_ac(true); combat_free_move = 2 * perk_level(obj_dude, PERK_BONUS_MOVE); intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } else { soundContinueAll(); } bool scriptOverrides = false; if (a1->sid != -1) { scr_set_objs(a1->sid, NULL, NULL); scr_set_ext_param(a1->sid, 4); exec_script_proc(a1->sid, SCRIPT_PROC_COMBAT); Script* scr; if (scr_ptr(a1->sid, &scr) != -1) { scriptOverrides = scr->scriptOverrides; } if (game_user_wants_to_quit == 1) { return -1; } } if (!scriptOverrides) { if (!a2 && critter_is_prone(a1)) { combat_standup(a1); } if (a1 == obj_dude) { game_ui_enable(); gmouse_3d_refresh(); if (gcsd != NULL) { combat_attack_this(gcsd->defender); } if (!a2) { combat_state |= 0x02; } intface_end_buttons_enable(); // NOTE: Uninline. combat_update_critters_in_los(0); if (combat_highlight != 0) { combat_outline_on(); } if (combat_input() == -1) { game_ui_disable(1); gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH); a1->data.critter.combat.damageLastTurn = 0; intface_end_buttons_disable(); combat_outline_off(); intface_update_move_points(-1, -1); intface_update_ac(true); combat_free_move = 0; return -1; } } else { Rect rect; if (obj_turn_on_outline(a1, &rect) == 0) { tile_refresh_rect(&rect, a1->elevation); } combat_ai(a1, gcsd != NULL ? gcsd->defender : NULL); } } while (combat_turn_running > 0) { process_bk(); } if (a1 == obj_dude) { game_ui_disable(1); gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH); intface_end_buttons_disable(); combat_outline_off(); intface_update_move_points(-1, -1); combat_turn_obj = NULL; intface_update_ac(true); combat_turn_obj = obj_dude; } else { Rect rect; if (obj_turn_off_outline(a1, &rect) == 0) { tile_refresh_rect(&rect, a1->elevation); } } } if ((obj_dude->data.critter.combat.results & DAM_DEAD) != 0) { return -1; } if (a1 != obj_dude || combat_elev == obj_dude->elevation) { combat_free_move = 0; return 0; } return -1; } // 0x422C60 static bool combat_should_end() { if (list_com <= 1) { return true; } int index; for (index = 0; index < list_com; index++) { if (combat_list[index] == obj_dude) { break; } } if (index == list_com) { return true; } int team = obj_dude->data.critter.combat.team; for (index = 0; index < list_com; index++) { Object* critter = combat_list[index]; if (critter->data.critter.combat.team != team) { break; } Object* critterWhoHitMe = critter->data.critter.combat.whoHitMe; if (critterWhoHitMe != NULL && critterWhoHitMe->data.critter.combat.team == team) { break; } } if (index == list_com) { return true; } return false; } // 0x422D2C void combat(STRUCT_664980* attack) { if (attack == NULL || (attack->attacker == NULL || attack->attacker->elevation == map_elevation) || (attack->defender == NULL || attack->defender->elevation == map_elevation)) { int v3 = combat_state & 0x01; combat_begin(NULL); int v6; // TODO: Not sure. if (v3 != 0) { if (combat_turn(obj_dude, true) == -1) { v6 = -1; } else { int index; for (index = 0; index < list_com; index++) { if (combat_list[index] == obj_dude) { break; } } v6 = index + 1; } gcsd = NULL; } else { Object* v3; Object* v9; if (attack != NULL) { v3 = attack->defender; v9 = attack->attacker; } else { v3 = NULL; v9 = NULL; } combat_sequence_init(v9, v3); gcsd = attack; v6 = 0; } do { if (v6 == -1) { break; } combat_set_move_all(); for (; v6 < list_com; v6++) { if (combat_turn(combat_list[v6], false) == -1) { break; } if (combat_ending_guy != NULL) { break; } gcsd = NULL; } if (v6 < list_com) { break; } combat_sequence(); v6 = 0; combatNumTurns += 1; } while (!combat_should_end()); if (combat_end_due_to_load) { game_ui_enable(); gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); } else { gmouse_disable_scrolling(); intface_end_window_close(true); gmouse_enable_scrolling(); combat_over(); scr_exec_map_update_scripts(); } combat_end_due_to_load = 0; if (game_user_wants_to_quit == 1) { game_user_wants_to_quit = 0; } } } // 0x422EC4 void combat_ctd_init(Attack* attack, Object* attacker, Object* defender, int hitMode, int hitLocation) { attack->attacker = attacker; attack->hitMode = hitMode; attack->weapon = item_hit_with(attacker, hitMode); attack->attackHitLocation = HIT_LOCATION_TORSO; attack->attackerDamage = 0; attack->attackerFlags = 0; attack->ammoQuantity = 0; attack->criticalMessageId = -1; attack->defender = defender; attack->tile = defender != NULL ? defender->tile : -1; attack->defenderHitLocation = hitLocation; attack->defenderDamage = 0; attack->defenderFlags = 0; attack->defenderKnockback = 0; attack->extrasLength = 0; attack->oops = defender; } // 0x422F3C int combat_attack(Object* a1, Object* a2, int hitMode, int hitLocation) { if (a1 != obj_dude && hitMode == HIT_MODE_PUNCH && roll_random(1, 4) == 1) { int fid = art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_KICK_LEG, (a1->fid & 0xF000) >> 12, (a1->fid & 0x70000000) >> 28); if (art_exists(fid)) { hitMode = HIT_MODE_KICK; } } combat_ctd_init(&main_ctd, a1, a2, hitMode, hitLocation); debug_printf("computing attack...\n"); if (compute_attack(&main_ctd) == -1) { return -1; } if (gcsd != NULL) { main_ctd.defenderDamage += gcsd->damageBonus; if (main_ctd.defenderDamage < gcsd->minDamage) { main_ctd.defenderDamage = gcsd->minDamage; } if (main_ctd.defenderDamage > gcsd->maxDamage) { main_ctd.defenderDamage = gcsd->maxDamage; } if (gcsd->field_1C) { // FIXME: looks like a bug, two different fields are used to set // one field. main_ctd.defenderFlags = gcsd->field_20; main_ctd.defenderFlags = gcsd->field_24; } } bool aiming; if (main_ctd.defenderHitLocation == HIT_LOCATION_TORSO || main_ctd.defenderHitLocation == HIT_LOCATION_UNCALLED) { if (a1 == obj_dude) { intface_get_attack(&hitMode, &aiming); } else { aiming = false; } } else { aiming = true; } int actionPoints = item_w_mp_cost(a1, main_ctd.hitMode, aiming); debug_printf("sequencing attack...\n"); if (action_attack(&main_ctd) == -1) { return -1; } if (actionPoints > a1->data.critter.combat.ap) { a1->data.critter.combat.ap = 0; } else { a1->data.critter.combat.ap -= actionPoints; } if (a1 == obj_dude) { intface_update_move_points(a1->data.critter.combat.ap, combat_free_move); critter_set_who_hit_me(a1, a2); } combat_call_display = 1; combat_cleanup_enabled = 1; combatAIInfoSetLastTarget(a1, a2); debug_printf("running attack...\n"); return 0; } // Returns tile one step closer from [a1] to [a2] // // 0x423104 int combat_bullet_start(const Object* a1, const Object* a2) { int rotation = tile_dir(a1->tile, a2->tile); return tile_num_in_direction(a1->tile, rotation, 1); } // 0x423128 static bool check_ranged_miss(Attack* attack) { int range = item_w_range(attack->attacker, attack->hitMode); int to = tile_num_beyond(attack->attacker->tile, attack->defender->tile, range); int roll = ROLL_FAILURE; Object* critter = attack->attacker; if (critter != NULL) { int curr = attack->attacker->tile; while (curr != to) { make_straight_path_func(attack->attacker, curr, to, NULL, &critter, 32, obj_shoot_blocking_at); if (critter != NULL) { if ((critter->flags & OBJECT_SHOOT_THRU) == 0) { if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) { roll = ROLL_SUCCESS; break; } if (critter != attack->defender) { int v6 = determine_to_hit_func(attack->attacker, attack->attacker->tile, critter, attack->defenderHitLocation, attack->hitMode, 1) / 3; if (critter_is_dead(critter)) { v6 = 5; } if (roll_random(1, 100) <= v6) { roll = ROLL_SUCCESS; break; } } curr = critter->tile; } } if (critter == NULL) { break; } } } attack->defenderHitLocation = HIT_LOCATION_TORSO; if (roll < ROLL_SUCCESS || critter == NULL || (critter->flags & OBJECT_SHOOT_THRU) == 0) { return false; } attack->defender = critter; attack->tile = critter->tile; attack->attackerFlags |= DAM_HIT; attack->defenderHitLocation = HIT_LOCATION_TORSO; compute_damage(attack, 1, 2); return true; } // 0x423284 static int shoot_along_path(Attack* attack, int endTile, int rounds, int anim) { // 0x56D3A0 static Attack temp_ctd; int remainingRounds = rounds; int roundsHitMainTarget = 0; int currentTile = attack->attacker->tile; Object* critter = attack->attacker; while (critter != NULL) { if ((remainingRounds <= 0 && anim != ANIM_FIRE_CONTINUOUS) || currentTile == endTile || attack->extrasLength >= 6) { break; } make_straight_path_func(attack->attacker, currentTile, endTile, NULL, &critter, 32, obj_shoot_blocking_at); if (critter != NULL) { if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) { break; } int accuracy = determine_to_hit_func(attack->attacker, attack->attacker->tile, critter, HIT_LOCATION_TORSO, attack->hitMode, 1); if (anim == ANIM_FIRE_CONTINUOUS) { remainingRounds = 1; } int roundsHit = 0; while (roll_random(1, 100) <= accuracy && remainingRounds > 0) { remainingRounds -= 1; roundsHit += 1; } if (roundsHit != 0) { if (critter == attack->defender) { roundsHitMainTarget += roundsHit; } else { int index; for (index = 0; index < attack->extrasLength; index += 1) { if (critter == attack->extras[index]) { break; } } attack->extrasHitLocation[index] = HIT_LOCATION_TORSO; attack->extras[index] = critter; combat_ctd_init(&temp_ctd, attack->attacker, critter, attack->hitMode, HIT_LOCATION_TORSO); temp_ctd.attackerFlags |= DAM_HIT; compute_damage(&temp_ctd, roundsHit, 2); if (index == attack->extrasLength) { attack->extrasDamage[index] = temp_ctd.defenderDamage; attack->extrasFlags[index] = temp_ctd.defenderFlags; attack->extrasKnockback[index] = temp_ctd.defenderKnockback; attack->extrasLength++; } else { if (anim == ANIM_FIRE_BURST) { attack->extrasDamage[index] += temp_ctd.defenderDamage; attack->extrasFlags[index] |= temp_ctd.defenderFlags; attack->extrasKnockback[index] += temp_ctd.defenderKnockback; } } } } currentTile = critter->tile; } } if (anim == ANIM_FIRE_CONTINUOUS) { roundsHitMainTarget = 0; } return roundsHitMainTarget; } // 0x423488 static int compute_spray(Attack* attack, int accuracy, int* roundsHitMainTargetPtr, int* roundsSpentPtr, int anim) { *roundsHitMainTargetPtr = 0; int ammoQuantity = item_w_curr_ammo(attack->weapon); int burstRounds = item_w_rounds(attack->weapon); if (burstRounds < ammoQuantity) { ammoQuantity = burstRounds; } *roundsSpentPtr = ammoQuantity; int criticalChance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE); int roll = roll_check(accuracy, criticalChance, NULL); if (roll == ROLL_CRITICAL_FAILURE) { return roll; } if (roll == ROLL_CRITICAL_SUCCESS) { accuracy += 20; } int leftRounds; int mainTargetRounds; int centerRounds; int rightRounds; if (anim == ANIM_FIRE_BURST) { centerRounds = ammoQuantity / 3; if (centerRounds == 0) { centerRounds = 1; } leftRounds = ammoQuantity / 3; rightRounds = ammoQuantity - centerRounds - leftRounds; mainTargetRounds = centerRounds / 2; if (mainTargetRounds == 0) { mainTargetRounds = 1; centerRounds -= 1; } } else { leftRounds = 1; mainTargetRounds = 1; centerRounds = 1; rightRounds = 1; } for (int index = 0; index < mainTargetRounds; index += 1) { if (roll_check(accuracy, 0, NULL) >= ROLL_SUCCESS) { *roundsHitMainTargetPtr += 1; } } if (*roundsHitMainTargetPtr == 0 && check_ranged_miss(attack)) { *roundsHitMainTargetPtr = 1; } int range = item_w_range(attack->attacker, attack->hitMode); int mainTargetEndTile = tile_num_beyond(attack->attacker->tile, attack->defender->tile, range); *roundsHitMainTargetPtr += shoot_along_path(attack, mainTargetEndTile, centerRounds - *roundsHitMainTargetPtr, anim); int centerTile; if (obj_dist(attack->attacker, attack->defender) <= 3) { centerTile = tile_num_beyond(attack->attacker->tile, attack->defender->tile, 3); } else { centerTile = attack->defender->tile; } int rotation = tile_dir(centerTile, attack->attacker->tile); int leftTile = tile_num_in_direction(centerTile, (rotation + 1) % ROTATION_COUNT, 1); int leftEndTile = tile_num_beyond(attack->attacker->tile, leftTile, range); *roundsHitMainTargetPtr += shoot_along_path(attack, leftEndTile, leftRounds, anim); int rightTile = tile_num_in_direction(centerTile, (rotation + 5) % ROTATION_COUNT, 1); int rightEndTile = tile_num_beyond(attack->attacker->tile, rightTile, range); *roundsHitMainTargetPtr += shoot_along_path(attack, rightEndTile, rightRounds, anim); if (roll != ROLL_FAILURE || (*roundsHitMainTargetPtr <= 0 && attack->extrasLength <= 0)) { if (roll >= ROLL_SUCCESS && *roundsHitMainTargetPtr == 0 && attack->extrasLength == 0) { roll = ROLL_FAILURE; } } else { roll = ROLL_SUCCESS; } return roll; } // 0x423714 static int correctAttackForPerks(Attack* attack) { if (item_w_perk(attack->weapon) == PERK_WEAPON_ENHANCED_KNOCKOUT) { int difficulty = critterGetStat(attack->attacker, STAT_STRENGTH) - 8; int chance = roll_random(1, 100); if (chance <= difficulty) { Object* weapon = NULL; if (attack->defender != obj_dude) { weapon = item_hit_with(attack->defender, HIT_MODE_RIGHT_WEAPON_PRIMARY); } if (!(attackFindInvalidFlags(attack->defender, weapon) & 1)) { attack->defenderFlags |= DAM_KNOCKED_OUT; } } } return 0; } // 0x42378C static int compute_attack(Attack* attack) { int range = item_w_range(attack->attacker, attack->hitMode); int distance = obj_dist(attack->attacker, attack->defender); if (range < distance) { return -1; } int anim = item_w_anim(attack->attacker, attack->hitMode); int accuracy = determine_to_hit_func(attack->attacker, attack->attacker->tile, attack->defender, attack->defenderHitLocation, attack->hitMode, 1); bool isGrenade = false; int damageType = item_w_damage_type(attack->attacker, attack->weapon); if (anim == ANIM_THROW_ANIM && (damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP)) { isGrenade = true; } if (attack->defenderHitLocation == HIT_LOCATION_UNCALLED) { attack->defenderHitLocation = HIT_LOCATION_TORSO; } int attackType = item_w_subtype(attack->weapon, attack->hitMode); int roundsHitMainTarget = 1; int damageMultiplier = 2; int roundsSpent = 1; int roll; if (anim == ANIM_FIRE_BURST || anim == ANIM_FIRE_CONTINUOUS) { roll = compute_spray(attack, accuracy, &roundsHitMainTarget, &roundsSpent, anim); } else { int chance = critterGetStat(attack->attacker, STAT_CRITICAL_CHANCE); roll = roll_check(accuracy, chance - hit_location_penalty[attack->defenderHitLocation], NULL); } if (roll == ROLL_FAILURE) { if (trait_level(TRAIT_JINXED) || perkHasRank(obj_dude, PERK_JINXED)) { if (roll_random(0, 1) == 1) { roll = ROLL_CRITICAL_FAILURE; } } } if (roll == ROLL_SUCCESS) { if ((attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) && attack->attacker == obj_dude) { if (perkHasRank(attack->attacker, PERK_SLAYER)) { roll = ROLL_CRITICAL_SUCCESS; } if (perkHasRank(obj_dude, PERK_SILENT_DEATH) && !is_hit_from_front(obj_dude, attack->defender) && is_pc_flag(DUDE_STATE_SNEAKING) && obj_dude != attack->defender->data.critter.combat.whoHitMe) { damageMultiplier = 4; } if (((attack->hitMode == HIT_MODE_HAMMER_PUNCH || attack->hitMode == HIT_MODE_POWER_KICK) && roll_random(1, 100) <= 5) || ((attack->hitMode == HIT_MODE_JAB || attack->hitMode == HIT_MODE_HOOK_KICK) && roll_random(1, 100) <= 10) || (attack->hitMode == HIT_MODE_HAYMAKER && roll_random(1, 100) <= 15) || (attack->hitMode == HIT_MODE_PALM_STRIKE && roll_random(1, 100) <= 20) || (attack->hitMode == HIT_MODE_PIERCING_STRIKE && roll_random(1, 100) <= 40) || (attack->hitMode == HIT_MODE_PIERCING_KICK && roll_random(1, 100) <= 50)) { roll = ROLL_CRITICAL_SUCCESS; } } } if (attackType == ATTACK_TYPE_RANGED) { attack->ammoQuantity = roundsSpent; if (roll == ROLL_SUCCESS && attack->attacker == obj_dude) { if (perk_level(obj_dude, PERK_SNIPER) != 0) { int d10 = roll_random(1, 10); int luck = critterGetStat(obj_dude, STAT_LUCK); if (d10 <= luck) { roll = ROLL_CRITICAL_SUCCESS; } } } } else { if (item_w_max_ammo(attack->weapon) > 0) { attack->ammoQuantity = 1; } } if (item_w_compute_ammo_cost(attack->weapon, &(attack->ammoQuantity)) == -1) { return -1; } switch (roll) { case ROLL_CRITICAL_SUCCESS: damageMultiplier = attack_crit_success(attack); // FALLTHROUGH case ROLL_SUCCESS: attack->attackerFlags |= DAM_HIT; correctAttackForPerks(attack); compute_damage(attack, roundsHitMainTarget, damageMultiplier); break; case ROLL_FAILURE: if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) { check_ranged_miss(attack); } break; case ROLL_CRITICAL_FAILURE: attack_crit_failure(attack); break; } if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) { if ((attack->attackerFlags & (DAM_HIT | DAM_CRITICAL)) == 0) { int tile; if (isGrenade) { int throwDistance = roll_random(1, distance / 2); if (throwDistance == 0) { throwDistance = 1; } int rotation = roll_random(0, 5); tile = tile_num_in_direction(attack->defender->tile, rotation, throwDistance); } else { tile = tile_num_beyond(attack->attacker->tile, attack->defender->tile, range); } attack->tile = tile; Object* v25 = attack->defender; make_straight_path_func(v25, attack->defender->tile, attack->tile, NULL, &v25, 32, obj_shoot_blocking_at); if (v25 != NULL && v25 != attack->defender) { attack->tile = v25->tile; } else { v25 = obj_blocking_at(NULL, attack->tile, attack->defender->elevation); } if (v25 != NULL && (v25->flags & OBJECT_SHOOT_THRU) == 0) { attack->attackerFlags |= DAM_HIT; attack->defender = v25; compute_damage(attack, 1, 2); } } } if ((damageType == DAMAGE_TYPE_EXPLOSION || isGrenade) && ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0)) { compute_explosion_on_extras(attack, 0, isGrenade, 0); } else { if ((attack->attackerFlags & DAM_EXPLODE) != 0) { compute_explosion_on_extras(attack, 1, isGrenade, 0); } } death_checks(attack); return 0; } // compute_explosion_on_extras // 0x423C10 void compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4) { // 0x56D458 Attack temp_ctd; Object* attacker; if (a2) { attacker = attack->attacker; } else { if ((attack->attackerFlags & DAM_HIT) != 0) { attacker = attack->defender; } else { attacker = NULL; } } int tile; if (attacker != NULL) { tile = attacker->tile; } else { tile = attack->tile; } if (tile == -1) { debug_printf("\nError: compute_explosion_on_extras: Called with bad target/tileNum"); return; } // TODO: The math in this loop is rather complex and hard to understand. int v20; int v22 = 0; int rotation = 0; int v5 = -1; int v19 = tile; while (attack->extrasLength < 6) { if (v22 != 0 && (v5 == -1 || (v5 = tile_num_in_direction(v5, rotation, 1)) != v19)) { v20++; if (v20 % v22 == 0) { rotation += 1; if (rotation == ROTATION_COUNT) { rotation = ROTATION_NE; } } } else { v22++; if (isGrenade && item_w_grenade_dmg_radius(attack->weapon) < v22) { v5 = -1; } else if (isGrenade || item_w_rocket_dmg_radius(attack->weapon) >= v22) { v5 = tile_num_in_direction(v19, ROTATION_NE, 1); } else { v5 = -1; } v19 = v5; rotation = ROTATION_SE; v20 = 0; } if (v5 == -1) { break; } Object* obstacle = obj_blocking_at(attacker, v5, attack->attacker->elevation); if (obstacle != NULL && FID_TYPE(obstacle->fid) == OBJ_TYPE_CRITTER && (obstacle->data.critter.combat.results & DAM_DEAD) == 0 && (obstacle->flags & OBJECT_SHOOT_THRU) == 0 && !combat_is_shot_blocked(obstacle, obstacle->tile, tile, NULL, NULL)) { if (obstacle == attack->attacker) { attack->attackerFlags &= ~DAM_HIT; compute_damage(attack, 1, 2); attack->attackerFlags |= DAM_HIT; attack->attackerFlags |= DAM_BACKWASH; } else { int index; for (index = 0; index < attack->extrasLength; index++) { if (attack->extras[index] == obstacle) { break; } } if (index == attack->extrasLength) { attack->extrasHitLocation[index] = HIT_LOCATION_TORSO; attack->extras[index] = obstacle; combat_ctd_init(&temp_ctd, attack->attacker, obstacle, attack->hitMode, HIT_LOCATION_TORSO); if (!a4) { temp_ctd.attackerFlags |= DAM_HIT; compute_damage(&temp_ctd, 1, 2); } attack->extrasDamage[index] = temp_ctd.defenderDamage; attack->extrasFlags[index] = temp_ctd.defenderFlags; attack->extrasKnockback[index] = temp_ctd.defenderKnockback; attack->extrasLength += 1; } } } } } // 0x423EB4 static int attack_crit_success(Attack* attack) { Object* defender = attack->defender; if (defender != NULL && critter_flag_check(defender->pid, CRITTER_INVULNERABLE)) { return 2; } if (defender != NULL && PID_TYPE(defender->pid) != OBJ_TYPE_CRITTER) { return 2; } attack->attackerFlags |= DAM_CRITICAL; int chance = roll_random(1, 100); chance += critterGetStat(attack->attacker, STAT_BETTER_CRITICALS); int effect; if (chance <= 20) effect = 0; else if (chance <= 45) effect = 1; else if (chance <= 70) effect = 2; else if (chance <= 90) effect = 3; else if (chance <= 100) effect = 4; else effect = 5; CriticalHitDescription* criticalHitDescription; if (defender == obj_dude) { criticalHitDescription = &(pc_crit_succ_eff[attack->defenderHitLocation][effect]); } else { int killType = critterGetKillType(defender); criticalHitDescription = &(crit_succ_eff[killType][attack->defenderHitLocation][effect]); } attack->defenderFlags |= criticalHitDescription->flags; // NOTE: Original code is slightly different, it does not set message in // advance, instead using "else" statement. attack->criticalMessageId = criticalHitDescription->messageId; if (criticalHitDescription->massiveCriticalStat != -1) { if (stat_result(defender, criticalHitDescription->massiveCriticalStat, criticalHitDescription->massiveCriticalStatModifier, NULL) <= ROLL_FAILURE) { attack->defenderFlags |= criticalHitDescription->massiveCriticalFlags; attack->criticalMessageId = criticalHitDescription->massiveCriticalMessageId; } } if ((attack->defenderFlags & DAM_CRIP_RANDOM) != 0) { // NOTE: Uninline. do_random_cripple(&(attack->defenderFlags)); } if (item_w_perk(attack->weapon) == PERK_WEAPON_ENHANCED_KNOCKOUT) { attack->defenderFlags |= DAM_KNOCKED_OUT; } Object* weapon = NULL; if (defender != obj_dude) { weapon = item_hit_with(defender, HIT_MODE_RIGHT_WEAPON_PRIMARY); } int flags = attackFindInvalidFlags(defender, weapon); attack->defenderFlags &= ~flags; return criticalHitDescription->damageMultiplier; } // 0x424088 static int attackFindInvalidFlags(Object* critter, Object* item) { int flags = 0; if (critter != NULL && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER && critter_flag_check(critter->pid, CRITTER_NO_DROP)) { flags |= DAM_DROP; } if (item != NULL && item_is_hidden(item)) { flags |= DAM_DROP; } return flags; } // 0x4240DC static int attack_crit_failure(Attack* attack) { attack->attackerFlags |= DAM_HIT; if (attack->attacker != NULL && critter_flag_check(attack->attacker->pid, CRITTER_INVULNERABLE)) { return 0; } if (attack->attacker == obj_dude) { unsigned int gameTime = game_time(); if (gameTime / GAME_TIME_TICKS_PER_DAY < 6) { return 0; } } int attackType = item_w_subtype(attack->weapon, attack->hitMode); int criticalFailureTableIndex = item_w_crit_fail(attack->weapon); if (criticalFailureTableIndex == -1) { criticalFailureTableIndex = 0; } int chance = roll_random(1, 100) - 5 * (critterGetStat(attack->attacker, STAT_LUCK) - 5); int effect; if (chance <= 20) effect = 0; else if (chance <= 50) effect = 1; else if (chance <= 75) effect = 2; else if (chance <= 95) effect = 3; else effect = 4; int flags = cf_table[criticalFailureTableIndex][effect]; if (flags == 0) { return 0; } attack->attackerFlags |= DAM_CRITICAL; attack->attackerFlags |= flags; int v17 = attackFindInvalidFlags(attack->attacker, attack->weapon); attack->attackerFlags &= ~v17; if ((attack->attackerFlags & DAM_HIT_SELF) != 0) { int ammoQuantity = attackType == ATTACK_TYPE_RANGED ? attack->ammoQuantity : 1; compute_damage(attack, ammoQuantity, 2); } else if ((attack->attackerFlags & DAM_EXPLODE) != 0) { compute_damage(attack, 1, 2); } if ((attack->attackerFlags & DAM_LOSE_TURN) != 0) { attack->attacker->data.critter.combat.ap = 0; } if ((attack->attackerFlags & DAM_LOSE_AMMO) != 0) { if (attackType == ATTACK_TYPE_RANGED) { attack->ammoQuantity = item_w_curr_ammo(attack->weapon); } else { attack->attackerFlags &= ~DAM_LOSE_AMMO; } } if ((attack->attackerFlags & DAM_CRIP_RANDOM) != 0) { // NOTE: Uninline. do_random_cripple(&(attack->attackerFlags)); } if ((attack->attackerFlags & DAM_RANDOM_HIT) != 0) { attack->defender = combat_ai_random_target(attack); if (attack->defender != NULL) { attack->attackerFlags |= DAM_HIT; attack->defenderHitLocation = HIT_LOCATION_TORSO; attack->attackerFlags &= ~DAM_CRITICAL; int ammoQuantity = attackType == ATTACK_TYPE_RANGED ? attack->ammoQuantity : 1; compute_damage(attack, ammoQuantity, 2); } else { attack->defender = attack->oops; } if (attack->defender != NULL) { attack->tile = attack->defender->tile; } } return 0; } // 0x42432C static void do_random_cripple(int* flagsPtr) { *flagsPtr &= ~DAM_CRIP_RANDOM; switch (roll_random(0, 3)) { case 0: *flagsPtr |= DAM_CRIP_LEG_LEFT; break; case 1: *flagsPtr |= DAM_CRIP_LEG_RIGHT; break; case 2: *flagsPtr |= DAM_CRIP_ARM_LEFT; break; case 3: *flagsPtr |= DAM_CRIP_ARM_RIGHT; break; } } // 0x42436C int determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode) { return determine_to_hit_func(a1, a1->tile, a2, hitLocation, hitMode, 1); } // 0x424380 int determine_to_hit_no_range(Object* a1, Object* a2, int hitLocation, int hitMode, unsigned char* a5) { return determine_to_hit_func(a1, a1->tile, a2, hitLocation, hitMode, 0); } // 0x424394 int determine_to_hit_from_tile(Object* a1, int tile, Object* a3, int hitLocation, int hitMode) { return determine_to_hit_func(a1, tile, a3, hitLocation, hitMode, 1); } // determine_to_hit // 0x4243A8 static int determine_to_hit_func(Object* attacker, int tile, Object* defender, int hitLocation, int hitMode, int a6) { Object* weapon = item_hit_with(attacker, hitMode); bool targetIsCritter = defender != NULL ? FID_TYPE(defender->fid) == OBJ_TYPE_CRITTER : false; bool isRangedWeapon = false; int accuracy; if (weapon == NULL || hitMode == HIT_MODE_PUNCH || hitMode == HIT_MODE_KICK || (hitMode >= FIRST_ADVANCED_UNARMED_HIT_MODE && hitMode <= LAST_ADVANCED_UNARMED_HIT_MODE)) { accuracy = skill_level(attacker, SKILL_UNARMED); } else { accuracy = item_w_skill_level(attacker, hitMode); int modifier = 0; int attackType = item_w_subtype(weapon, hitMode); if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW) { isRangedWeapon = true; int v29 = 0; int v25 = 0; int weaponPerk = item_w_perk(weapon); switch (weaponPerk) { case PERK_WEAPON_LONG_RANGE: v29 = 4; break; case PERK_WEAPON_SCOPE_RANGE: v29 = 5; v25 = 8; break; default: v29 = 2; break; } int perception = critterGetStat(attacker, STAT_PERCEPTION); if (defender != NULL) { modifier = obj_dist_with_tile(attacker, tile, defender, defender->tile); } else { modifier = 0; } if (modifier >= v25) { int penalty = attacker == obj_dude ? v29 * (perception - 2) : v29 * perception; modifier -= penalty; } else { modifier += v25; } if (-2 * perception > modifier) { modifier = -2 * perception; } if (attacker == obj_dude) { modifier -= 2 * perk_level(obj_dude, PERK_SHARPSHOOTER); } if (modifier >= 0) { if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) { modifier *= -12; } else { modifier *= -4; } } else { modifier *= -4; } if (a6 || modifier > 0) { accuracy += modifier; } modifier = 0; if (defender != NULL && a6) { combat_is_shot_blocked(attacker, tile, defender->tile, defender, &modifier); } accuracy -= 10 * modifier; } if (attacker == obj_dude && trait_level(TRAIT_ONE_HANDER)) { if (item_w_is_2handed(weapon)) { accuracy -= 40; } else { accuracy += 20; } } int minStrength = item_w_min_st(weapon); modifier = minStrength - critterGetStat(attacker, STAT_STRENGTH); if (attacker == obj_dude && perk_level(obj_dude, PERK_WEAPON_HANDLING) != 0) { modifier -= 3; } if (modifier > 0) { accuracy -= 20 * modifier; } if (item_w_perk(weapon) == PERK_WEAPON_ACCURATE) { accuracy += 20; } } if (targetIsCritter && defender != NULL) { int armorClass = critterGetStat(defender, STAT_ARMOR_CLASS); armorClass += item_w_ac_adjust(weapon); if (armorClass < 0) { armorClass = 0; } accuracy -= armorClass; } if (isRangedWeapon) { accuracy += hit_location_penalty[hitLocation]; } else { accuracy += hit_location_penalty[hitLocation] / 2; } if (defender != NULL && (defender->flags & OBJECT_MULTIHEX) != 0) { accuracy += 15; } if (attacker == obj_dude) { int lightIntensity; if (defender != NULL) { lightIntensity = obj_get_visible_light(defender); if (item_w_perk(weapon) == PERK_WEAPON_NIGHT_SIGHT) { lightIntensity = 65536; } } else { lightIntensity = 0; } if (lightIntensity <= 26214) accuracy -= 40; else if (lightIntensity <= 39321) accuracy -= 25; else if (lightIntensity <= 52428) accuracy -= 10; } if (gcsd != NULL) { accuracy += gcsd->accuracyBonus; } if ((attacker->data.critter.combat.results & DAM_BLIND) != 0) { accuracy -= 25; } if (targetIsCritter && defender != NULL && (defender->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) { accuracy += 40; } if (attacker->data.critter.combat.team != obj_dude->data.critter.combat.team) { int combatDifficuly = 1; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficuly); switch (combatDifficuly) { case 0: accuracy -= 20; break; case 2: accuracy += 20; break; } } if (accuracy > 95) { accuracy = 95; } if (accuracy < -100) { debug_printf("Whoa! Bad skill value in determine_to_hit!\n"); } return accuracy; } // 0x4247B8 static void compute_damage(Attack* attack, int ammoQuantity, int bonusDamageMultiplier) { int* damagePtr; Object* critter; int* flagsPtr; int* knockbackDistancePtr; if ((attack->attackerFlags & DAM_HIT) != 0) { damagePtr = &(attack->defenderDamage); critter = attack->defender; flagsPtr = &(attack->defenderFlags); knockbackDistancePtr = &(attack->defenderKnockback); } else { damagePtr = &(attack->attackerDamage); critter = attack->attacker; flagsPtr = &(attack->attackerFlags); knockbackDistancePtr = NULL; } *damagePtr = 0; if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) { return; } int damageType = item_w_damage_type(attack->attacker, attack->weapon); int damageThreshold = critterGetStat(critter, STAT_DAMAGE_THRESHOLD + damageType); int damageResistance = critterGetStat(critter, STAT_DAMAGE_RESISTANCE + damageType); if ((*flagsPtr & DAM_BYPASS) != 0 && damageType != DAMAGE_TYPE_EMP) { damageThreshold = 20 * damageThreshold / 100; damageResistance = 20 * damageResistance / 100; } else { if (item_w_perk(attack->weapon) == PERK_WEAPON_PENETRATE || attack->hitMode == HIT_MODE_PALM_STRIKE || attack->hitMode == HIT_MODE_PIERCING_STRIKE || attack->hitMode == HIT_MODE_HOOK_KICK || attack->hitMode == HIT_MODE_PIERCING_KICK) { damageThreshold = 20 * damageThreshold / 100; } if (attack->attacker == obj_dude && trait_level(TRAIT_FINESSE)) { damageResistance += 30; } } int damageBonus; if (attack->attacker == obj_dude && item_w_subtype(attack->weapon, attack->hitMode) == ATTACK_TYPE_RANGED) { damageBonus = 2 * perk_level(obj_dude, PERK_BONUS_RANGED_DAMAGE); } else { damageBonus = 0; } int combatDifficultyDamageModifier = 100; if (attack->attacker->data.critter.combat.team != obj_dude->data.critter.combat.team) { int combatDifficulty = COMBAT_DIFFICULTY_NORMAL; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficulty); switch (combatDifficulty) { case COMBAT_DIFFICULTY_EASY: combatDifficultyDamageModifier = 75; break; case COMBAT_DIFFICULTY_HARD: combatDifficultyDamageModifier = 125; break; } } damageResistance += item_w_dr_adjust(attack->weapon); if (damageResistance > 100) { damageResistance = 100; } else if (damageResistance < 0) { damageResistance = 0; } int damageMultiplier = bonusDamageMultiplier * item_w_dam_mult(attack->weapon); int damageDivisor = item_w_dam_div(attack->weapon); for (int index = 0; index < ammoQuantity; index++) { int damage = item_w_damage(attack->attacker, attack->hitMode); damage += damageBonus; damage *= damageMultiplier; if (damageDivisor != 0) { damage /= damageDivisor; } // TODO: Why we're halving it? damage /= 2; damage *= combatDifficultyDamageModifier; damage /= 100; damage -= damageThreshold; if (damage > 0) { damage -= damage * damageResistance / 100; } if (damage > 0) { *damagePtr += damage; } } if (attack->attacker == obj_dude) { if (perk_level(attack->attacker, PERK_LIVING_ANATOMY) != 0) { int kt = critterGetKillType(attack->defender); if (kt != KILL_TYPE_ROBOT && kt != KILL_TYPE_ALIEN) { *damagePtr += 5; } } if (perk_level(attack->attacker, PERK_PYROMANIAC) != 0) { if (item_w_damage_type(attack->attacker, attack->weapon) == DAMAGE_TYPE_FIRE) { *damagePtr += 5; } } } if (knockbackDistancePtr != NULL && (critter->flags & OBJECT_MULTIHEX) == 0 && (damageType == DAMAGE_TYPE_EXPLOSION || attack->weapon == NULL || item_w_subtype(attack->weapon, attack->hitMode) == ATTACK_TYPE_MELEE) && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER && critter_flag_check(critter->pid, CRITTER_NO_KNOCKBACK) == 0) { bool shouldKnockback = true; bool hasStonewall = false; if (critter == obj_dude) { if (perk_level(critter, PERK_STONEWALL) != 0) { int chance = roll_random(0, 100); hasStonewall = true; if (chance < 50) { shouldKnockback = false; } } } if (shouldKnockback) { int knockbackDistanceDivisor = item_w_perk(attack->weapon) == PERK_WEAPON_KNOCKBACK ? 5 : 10; *knockbackDistancePtr = *damagePtr / knockbackDistanceDivisor; if (hasStonewall) { *knockbackDistancePtr /= 2; } } } } // 0x424BAC void death_checks(Attack* attack) { check_for_death(attack->attacker, attack->attackerDamage, &(attack->attackerFlags)); check_for_death(attack->defender, attack->defenderDamage, &(attack->defenderFlags)); for (int index = 0; index < attack->extrasLength; index++) { check_for_death(attack->extras[index], attack->extrasDamage[index], &(attack->extrasFlags[index])); } } // 0x424C04 void apply_damage(Attack* attack, bool animated) { Object* attacker = attack->attacker; bool attackerIsCritter = attacker != NULL && FID_TYPE(attacker->fid) == OBJ_TYPE_CRITTER; bool v5 = attack->defender != attack->oops; if (attackerIsCritter && (attacker->data.critter.combat.results & DAM_DEAD) != 0) { set_new_results(attacker, attack->attackerFlags); // TODO: Not sure about "attack->defender == attack->oops". damage_object(attacker, attack->attackerDamage, animated, attack->defender == attack->oops, attacker); } Object* v7 = attack->oops; if (v7 != NULL && v7 != attack->defender) { combatai_notify_onlookers(v7); } Object* defender = attack->defender; bool defenderIsCritter = defender != NULL && FID_TYPE(defender->fid) == OBJ_TYPE_CRITTER; if (!defenderIsCritter && !v5) { bool v9 = isPartyMember(attack->defender) && isPartyMember(attack->attacker) ? false : true; if (v9) { if (defender != NULL) { if (defender->sid != -1) { scr_set_ext_param(defender->sid, attack->attackerDamage); scr_set_objs(defender->sid, attack->attacker, attack->weapon); exec_script_proc(defender->sid, SCRIPT_PROC_DAMAGE); } } } } if (defenderIsCritter && (defender->data.critter.combat.results & DAM_DEAD) == 0) { set_new_results(defender, attack->defenderFlags); if (defenderIsCritter) { if (defenderIsCritter) { if ((defender->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) { if (!v5 || defender != obj_dude) { critter_set_who_hit_me(defender, attack->attacker); } } else if (defender == attack->oops || defender->data.critter.combat.team != attack->attacker->data.critter.combat.team) { combatai_check_retaliation(defender, attack->attacker); } } } scr_set_objs(defender->sid, attack->attacker, attack->weapon); damage_object(defender, attack->defenderDamage, animated, attack->defender != attack->oops, attacker); if (defenderIsCritter) { combatai_notify_onlookers(defender); } if (attack->defenderDamage >= 0 && (attack->attackerFlags & DAM_HIT) != 0) { scr_set_objs(attack->attacker->sid, NULL, attack->defender); scr_set_ext_param(attack->attacker->sid, 2); exec_script_proc(attack->attacker->sid, SCRIPT_PROC_COMBAT); } } for (int index = 0; index < attack->extrasLength; index++) { Object* obj = attack->extras[index]; if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER && (obj->data.critter.combat.results & DAM_DEAD) == 0) { set_new_results(obj, attack->extrasFlags[index]); if (defenderIsCritter) { if ((obj->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) { critter_set_who_hit_me(obj, attack->attacker); } else if (obj->data.critter.combat.team != attack->attacker->data.critter.combat.team) { combatai_check_retaliation(obj, attack->attacker); } } scr_set_objs(obj->sid, attack->attacker, attack->weapon); // TODO: Not sure about defender == oops. damage_object(obj, attack->extrasDamage[index], animated, attack->defender == attack->oops, attack->attacker); combatai_notify_onlookers(obj); if (attack->extrasDamage[index] >= 0) { if ((attack->attackerFlags & DAM_HIT) != 0) { scr_set_objs(attack->attacker->sid, NULL, obj); scr_set_ext_param(attack->attacker->sid, 2); exec_script_proc(attack->attacker->sid, SCRIPT_PROC_COMBAT); } } } } } // 0x424EE8 static void check_for_death(Object* object, int damage, int* flags) { if (object == NULL || !critter_flag_check(object->pid, CRITTER_INVULNERABLE)) { if (object == NULL || PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { if (damage > 0) { if (critter_get_hits(object) - damage <= 0) { *flags |= DAM_DEAD; } } } } } // 0x424F2C static void set_new_results(Object* critter, int flags) { if (critter == NULL) { return; } if (FID_TYPE(critter->fid) != OBJ_TYPE_CRITTER) { return; } if (critter_flag_check(critter->pid, CRITTER_INVULNERABLE)) { return; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return; } if ((flags & DAM_DEAD) != 0) { queue_remove(critter); } else if ((flags & DAM_KNOCKED_OUT) != 0) { int endurance = critterGetStat(critter, STAT_ENDURANCE); queue_add(10 * (35 - 3 * endurance), critter, NULL, EVENT_TYPE_KNOCKOUT); } if (critter == obj_dude && (flags & DAM_CRIP_ARM_ANY) != 0) { critter->data.critter.combat.results |= flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_CRIP | DAM_DEAD | DAM_LOSE_TURN); int leftItemAction; int rightItemAction; intface_get_item_states(&leftItemAction, &rightItemAction); intface_update_items(true, leftItemAction, rightItemAction); } else { critter->data.critter.combat.results |= flags & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN | DAM_CRIP | DAM_DEAD | DAM_LOSE_TURN); } } // 0x425020 static void damage_object(Object* a1, int damage, bool animated, int a4, Object* a5) { if (a1 == NULL) { return; } if (FID_TYPE(a1->fid) != OBJ_TYPE_CRITTER) { return; } if (critter_flag_check(a1->pid, CRITTER_INVULNERABLE)) { return; } if (damage <= 0) { return; } critter_adjust_hits(a1, -damage); if (a1 == obj_dude) { intface_update_hit_points(animated); } a1->data.critter.combat.damageLastTurn += damage; if (!a4) { // TODO: Not sure about this one. if (!isPartyMember(a1) || !isPartyMember(a5)) { scr_set_ext_param(a1->sid, damage); exec_script_proc(a1->sid, SCRIPT_PROC_DAMAGE); } } if ((a1->data.critter.combat.results & DAM_DEAD) != 0) { scr_set_objs(a1->sid, a1->data.critter.combat.whoHitMe, NULL); exec_script_proc(a1->sid, SCRIPT_PROC_DESTROY); item_destroy_all_hidden(a1); if (a1 != obj_dude) { Object* whoHitMe = a1->data.critter.combat.whoHitMe; if (whoHitMe == obj_dude || (whoHitMe != NULL && whoHitMe->data.critter.combat.team == obj_dude->data.critter.combat.team)) { bool scriptOverrides = false; Script* scr; if (scr_ptr(a1->sid, &scr) != -1) { scriptOverrides = scr->scriptOverrides; } if (!scriptOverrides) { combat_exps += critter_kill_exps(a1); critter_kill_count_inc(critterGetKillType(a1)); } } } if (a1->sid != -1) { scr_remove(a1->sid); a1->sid = -1; } partyMemberRemove(a1); } } // Print attack description to monitor. // // 0x425170 void combat_display(Attack* attack) { MessageListItem messageListItem; if (attack->attacker == obj_dude) { Object* weapon = item_hit_with(attack->attacker, attack->hitMode); int strengthRequired = item_w_min_st(weapon); if (perk_level(attack->attacker, PERK_WEAPON_HANDLING) != 0) { strengthRequired -= 3; } if (weapon != NULL) { if (strengthRequired > critterGetStat(obj_dude, STAT_STRENGTH)) { // You are not strong enough to use this weapon properly. messageListItem.num = 107; if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } Object* mainCritter; if ((attack->attackerFlags & DAM_HIT) != 0) { mainCritter = attack->defender; } else { mainCritter = attack->attacker; } char* mainCritterName = _a_1; char you[20]; you[0] = '\0'; if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) { // You (male) messageListItem.num = 506; } else { // You (female) messageListItem.num = 556; } if (message_search(&combat_message_file, &messageListItem)) { strcpy(you, messageListItem.text); } int baseMessageId; if (mainCritter == obj_dude) { mainCritterName = you; if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) { baseMessageId = 500; } else { baseMessageId = 550; } } else if (mainCritter != NULL) { mainCritterName = object_name(mainCritter); if (critterGetStat(mainCritter, STAT_GENDER) == GENDER_MALE) { baseMessageId = 600; } else { baseMessageId = 700; } } char text[280]; if (attack->defender != NULL && attack->oops != NULL && attack->defender != attack->oops && (attack->attackerFlags & DAM_HIT) != 0) { if (FID_TYPE(attack->defender->fid) == OBJ_TYPE_CRITTER) { if (attack->oops == obj_dude) { // 608 (male) - Oops! %s was hit instead of you! // 708 (female) - Oops! %s was hit instead of you! messageListItem.num = baseMessageId + 8; if (message_search(&combat_message_file, &messageListItem)) { sprintf(text, messageListItem.text, mainCritterName); } } else { // 509 (male) - Oops! %s were hit instead of %s! // 559 (female) - Oops! %s were hit instead of %s! const char* name = object_name(attack->oops); messageListItem.num = baseMessageId + 9; if (message_search(&combat_message_file, &messageListItem)) { sprintf(text, messageListItem.text, mainCritterName, name); } } } else { if (attack->attacker == obj_dude) { if (critterGetStat(attack->attacker, STAT_GENDER) == GENDER_MALE) { // (male) %s missed messageListItem.num = 515; } else { // (female) %s missed messageListItem.num = 565; } if (message_search(&combat_message_file, &messageListItem)) { sprintf(text, messageListItem.text, you); } } else { const char* name = object_name(attack->attacker); if (critterGetStat(attack->attacker, STAT_GENDER) == GENDER_MALE) { // (male) %s missed messageListItem.num = 615; } else { // (female) %s missed messageListItem.num = 715; } if (message_search(&combat_message_file, &messageListItem)) { sprintf(text, messageListItem.text, name); } } } strcat(text, "."); display_print(text); } if ((attack->attackerFlags & DAM_HIT) != 0) { Object* v21 = attack->defender; if (v21 != NULL && (v21->data.critter.combat.results & DAM_DEAD) == 0) { text[0] = '\0'; if (FID_TYPE(v21->fid) == OBJ_TYPE_CRITTER) { if (attack->defenderHitLocation == HIT_LOCATION_TORSO) { if ((attack->attackerFlags & DAM_CRITICAL) != 0) { switch (attack->defenderDamage) { case 0: // 528 - %s were critically hit for no damage messageListItem.num = baseMessageId + 28; break; case 1: // 524 - %s were critically hit for 1 hit point messageListItem.num = baseMessageId + 24; break; default: // 520 - %s were critically hit for %d hit points messageListItem.num = baseMessageId + 20; break; } if (message_search(&combat_message_file, &messageListItem)) { if (attack->defenderDamage <= 1) { sprintf(text, messageListItem.text, mainCritterName); } else { sprintf(text, messageListItem.text, mainCritterName, attack->defenderDamage); } } } else { combat_display_hit(text, v21, attack->defenderDamage); } } else { const char* hitLocationName = combat_get_loc_name(v21, attack->defenderHitLocation); if (hitLocationName != NULL) { if ((attack->attackerFlags & DAM_CRITICAL) != 0) { switch (attack->defenderDamage) { case 0: // 525 - %s were critically hit in %s for no damage messageListItem.num = baseMessageId + 25; break; case 1: // 521 - %s were critically hit in %s for 1 damage messageListItem.num = baseMessageId + 21; break; default: // 511 - %s were critically hit in %s for %d hit points messageListItem.num = baseMessageId + 11; break; } } else { switch (attack->defenderDamage) { case 0: // 526 - %s were hit in %s for no damage messageListItem.num = baseMessageId + 26; break; case 1: // 522 - %s were hit in %s for 1 damage messageListItem.num = baseMessageId + 22; break; default: // 512 - %s were hit in %s for %d hit points messageListItem.num = baseMessageId + 12; break; } } if (message_search(&combat_message_file, &messageListItem)) { if (attack->defenderDamage <= 1) { sprintf(text, messageListItem.text, mainCritterName, hitLocationName); } else { sprintf(text, messageListItem.text, mainCritterName, hitLocationName, attack->defenderDamage); } } } } int combatMessages = 1; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, &combatMessages); if (combatMessages == 1 && (attack->attackerFlags & DAM_CRITICAL) != 0 && attack->criticalMessageId != -1) { messageListItem.num = attack->criticalMessageId; if (message_search(&combat_message_file, &messageListItem)) { strcat(text, messageListItem.text); } if ((attack->defenderFlags & DAM_DEAD) != 0) { strcat(text, "."); display_print(text); if (attack->defender == obj_dude) { if (critterGetStat(attack->defender, STAT_GENDER) == GENDER_MALE) { // were killed messageListItem.num = 207; } else { // were killed messageListItem.num = 257; } } else { if (critterGetStat(attack->defender, STAT_GENDER) == GENDER_MALE) { // was killed messageListItem.num = 307; } else { // was killed messageListItem.num = 407; } } if (message_search(&combat_message_file, &messageListItem)) { sprintf(text, "%s %s", mainCritterName, messageListItem.text); } } } else { combat_display_flags(text, attack->defenderFlags, attack->defender); } strcat(text, "."); display_print(text); } } } if (attack->attacker != NULL && (attack->attacker->data.critter.combat.results & DAM_DEAD) == 0) { if ((attack->attackerFlags & DAM_HIT) == 0) { if ((attack->attackerFlags & DAM_CRITICAL) != 0) { switch (attack->attackerDamage) { case 0: // 514 - %s critically missed messageListItem.num = baseMessageId + 14; break; case 1: // 533 - %s critically missed and took 1 hit point messageListItem.num = baseMessageId + 33; break; default: // 534 - %s critically missed and took %d hit points messageListItem.num = baseMessageId + 34; break; } } else { // 515 - %s missed messageListItem.num = baseMessageId + 15; } if (message_search(&combat_message_file, &messageListItem)) { if (attack->attackerDamage <= 1) { sprintf(text, messageListItem.text, mainCritterName); } else { sprintf(text, messageListItem.text, mainCritterName, attack->attackerDamage); } } combat_display_flags(text, attack->attackerFlags, attack->attacker); strcat(text, "."); display_print(text); } if ((attack->attackerFlags & DAM_HIT) != 0 || (attack->attackerFlags & DAM_CRITICAL) == 0) { if (attack->attackerDamage > 0) { combat_display_hit(text, attack->attacker, attack->attackerDamage); combat_display_flags(text, attack->attackerFlags, attack->attacker); strcat(text, "."); display_print(text); } } } for (int index = 0; index < attack->extrasLength; index++) { Object* critter = attack->extras[index]; if ((critter->data.critter.combat.results & DAM_DEAD) == 0) { combat_display_hit(text, critter, attack->extrasDamage[index]); combat_display_flags(text, attack->extrasFlags[index], critter); strcat(text, "."); display_print(text); } } } // 0x425A9C static void combat_display_hit(char* dest, Object* critter, int damage) { MessageListItem messageListItem; char text[40]; char* name; int messageId; if (critter == obj_dude) { text[0] = '\0'; if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) { messageId = 500; } else { messageId = 550; } // 506 - You messageListItem.num = messageId + 6; if (message_search(&combat_message_file, &messageListItem)) { strcpy(text, messageListItem.text); } name = text; } else { name = object_name(critter); if (critterGetStat(critter, STAT_GENDER) == GENDER_MALE) { messageId = 600; } else { messageId = 700; } } switch (damage) { case 0: // 627 - %s was hit for no damage messageId += 27; break; case 1: // 623 - %s was hit for 1 hit point messageId += 23; break; default: // 613 - %s was hit for %d hit points messageId += 13; break; } messageListItem.num = messageId; if (message_search(&combat_message_file, &messageListItem)) { if (damage <= 1) { sprintf(dest, messageListItem.text, name); } else { sprintf(dest, messageListItem.text, name, damage); } } } // 0x425BA4 static void combat_display_flags(char* dest, int flags, Object* critter) { MessageListItem messageListItem; int num; if (critter == obj_dude) { if (critterGetStat(critter, STAT_GENDER) == GENDER_MALE) { num = 200; } else { num = 250; } } else { if (critterGetStat(critter, STAT_GENDER) == GENDER_MALE) { num = 300; } else { num = 400; } } if (flags == 0) { return; } if ((flags & DAM_DEAD) != 0) { // " and " messageListItem.num = 108; if (message_search(&combat_message_file, &messageListItem)) { strcat(dest, messageListItem.text); } // were killed messageListItem.num = num + 7; if (message_search(&combat_message_file, &messageListItem)) { strcat(dest, messageListItem.text); } return; } int bit = 1; int flagsListLength = 0; int flagsList[32]; for (int index = 0; index < 32; index++) { if (bit != DAM_CRITICAL && bit != DAM_HIT && (bit & flags) != 0) { flagsList[flagsListLength++] = index; } bit <<= 1; } if (flagsListLength != 0) { for (int index = 0; index < flagsListLength - 1; index++) { strcat(dest, ", "); messageListItem.num = num + flagsList[index]; if (message_search(&combat_message_file, &messageListItem)) { strcat(dest, messageListItem.text); } } // " and " messageListItem.num = 108; if (message_search(&combat_message_file, &messageListItem)) { strcat(dest, messageListItem.text); } messageListItem.num = flagsList[flagsListLength - 1]; if (message_search(&combat_message_file, &messageListItem)) { strcat(dest, messageListItem.text); } } } // 0x425E3C void combat_anim_begin() { if (++combat_turn_running == 1 && obj_dude == main_ctd.attacker) { game_ui_disable(1); gmouse_set_cursor(26); if (combat_highlight == 2) { combat_outline_off(); } } } // 0x425E80 void combat_anim_finished() { combat_turn_running -= 1; if (combat_turn_running != 0) { return; } if (obj_dude == main_ctd.attacker) { game_ui_enable(); } if (combat_cleanup_enabled) { combat_cleanup_enabled = false; Object* weapon = item_hit_with(main_ctd.attacker, main_ctd.hitMode); if (weapon != NULL) { if (item_w_max_ammo(weapon) > 0) { int ammoQuantity = item_w_curr_ammo(weapon); item_w_set_curr_ammo(weapon, ammoQuantity - main_ctd.ammoQuantity); if (main_ctd.attacker == obj_dude) { intface_update_ammo_lights(); } } } if (combat_call_display) { combat_display(&main_ctd); combat_call_display = false; } apply_damage(&main_ctd, true); Object* attacker = main_ctd.attacker; if (attacker == obj_dude && combat_highlight == 2) { combat_outline_on(); } if (scr_end_combat()) { if ((obj_dude->data.critter.combat.results & DAM_KNOCKED_OUT) != 0) { if (attacker->data.critter.combat.team == obj_dude->data.critter.combat.team) { combat_ending_guy = obj_dude->data.critter.combat.whoHitMe; } else { combat_ending_guy = attacker; } } } combat_ctd_init(&main_ctd, main_ctd.attacker, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); if ((attacker->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) { if ((attacker->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) == 0) { combat_standup(attacker); } } } } // 0x425FBC static void combat_standup(Object* a1) { int v2; v2 = 3; if (a1 == obj_dude && perk_level(a1, PERK_QUICK_RECOVERY)) { v2 = 1; } if (v2 > a1->data.critter.combat.ap) { a1->data.critter.combat.ap = 0; } else { a1->data.critter.combat.ap -= v2; } if (a1 == obj_dude) { intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } dude_standup(a1); // NOTE: Uninline. combat_turn_run(); } // Render two digits. // // 0x42603C static void print_tohit(unsigned char* dest, int destPitch, int accuracy) { CacheEntry* numbersFrmHandle; int numbersFrmFid = art_id(OBJ_TYPE_INTERFACE, 82, 0, 0, 0); unsigned char* numbersFrmData = art_ptr_lock_data(numbersFrmFid, 0, 0, &numbersFrmHandle); if (numbersFrmData == NULL) { return; } if (accuracy >= 0) { buf_to_buf(numbersFrmData + 9 * (accuracy % 10), 9, 17, 360, dest + 9, destPitch); buf_to_buf(numbersFrmData + 9 * (accuracy / 10), 9, 17, 360, dest, destPitch); } else { buf_to_buf(numbersFrmData + 108, 6, 17, 360, dest + 9, destPitch); buf_to_buf(numbersFrmData + 108, 6, 17, 360, dest, destPitch); } art_ptr_unlock(numbersFrmHandle); } // 0x42612C static char* combat_get_loc_name(Object* critter, int hitLocation) { MessageListItem messageListItem; messageListItem.num = 1000 + 10 * art_alias_num(critter->fid & 0xFFF) + hitLocation; if (message_search(&combat_message_file, &messageListItem)) { return messageListItem.text; } return NULL; } // 0x4261B4 static void draw_loc_off(int a1, int a2) { draw_loc(a2, colorTable[992]); } // 0x4261C0 static void draw_loc_on(int a1, int a2) { draw_loc(a2, colorTable[31744]); } // 0x4261CC static void draw_loc(int eventCode, int color) { color |= 0x3000000; if (eventCode >= 4) { char* name = combat_get_loc_name(call_target, hit_loc_right[eventCode - 4]); int width = text_width(name); win_print(call_win, name, 0, 431 - width, call_ty[eventCode - 4] - 86, color); } else { char* name = combat_get_loc_name(call_target, hit_loc_left[eventCode]); win_print(call_win, name, 0, 74, call_ty[eventCode] - 86, color); } } // 0x426218 static int get_called_shot_location(Object* critter, int* hitLocation, int hitMode) { if (critter == NULL) { return 0; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return 0; } call_target = critter; int calledShotWindowX = CALLED_SHOT_WINDOW_X; int calledShotWindowY = CALLED_SHOT_WINDOW_Y; call_win = win_add(calledShotWindowX, calledShotWindowY, CALLED_SHOT_WINDOW_WIDTH, CALLED_SHOT_WINDOW_HEIGHT, colorTable[0], WINDOW_FLAG_0x10); if (call_win == -1) { return -1; } int fid; CacheEntry* handle; unsigned char* data; unsigned char* windowBuffer = win_get_buf(call_win); fid = art_id(OBJ_TYPE_INTERFACE, 118, 0, 0, 0); data = art_ptr_lock_data(fid, 0, 0, &handle); if (data == NULL) { win_delete(call_win); return -1; } buf_to_buf(data, CALLED_SHOT_WINDOW_WIDTH, CALLED_SHOT_WINDOW_HEIGHT, CALLED_SHOT_WINDOW_WIDTH, windowBuffer, CALLED_SHOT_WINDOW_WIDTH); art_ptr_unlock(handle); fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_CALLED_SHOT_PIC, 0, 0); data = art_ptr_lock_data(fid, 0, 0, &handle); if (data != NULL) { buf_to_buf(data, 170, 225, 170, windowBuffer + CALLED_SHOT_WINDOW_WIDTH * 31 + 168, CALLED_SHOT_WINDOW_WIDTH); art_ptr_unlock(handle); } fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); CacheEntry* upHandle; unsigned char* up = art_ptr_lock_data(fid, 0, 0, &upHandle); if (up == NULL) { win_delete(call_win); return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); CacheEntry* downHandle; unsigned char* down = art_ptr_lock_data(fid, 0, 0, &downHandle); if (down == NULL) { art_ptr_unlock(upHandle); win_delete(call_win); return -1; } // Cancel button int btn = win_register_button(call_win, 210, 268, 15, 16, -1, -1, -1, KEY_ESCAPE, up, down, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } int oldFont = text_curr(); text_font(101); for (int index = 0; index < 4; index++) { int probability; int btn; probability = determine_to_hit(obj_dude, critter, hit_loc_left[index], hitMode); print_tohit(windowBuffer + CALLED_SHOT_WINDOW_WIDTH * (call_ty[index] - 86) + 33, CALLED_SHOT_WINDOW_WIDTH, probability); btn = win_register_button(call_win, 33, call_ty[index] - 90, 128, 20, index, index, -1, index, NULL, NULL, NULL, 0); win_register_button_func(btn, draw_loc_on, draw_loc_off, NULL, NULL); draw_loc(index, colorTable[992]); probability = determine_to_hit(obj_dude, critter, hit_loc_right[index], hitMode); print_tohit(windowBuffer + CALLED_SHOT_WINDOW_WIDTH * (call_ty[index] - 86) + 453, CALLED_SHOT_WINDOW_WIDTH, probability); btn = win_register_button(call_win, 341, call_ty[index] - 90, 128, 20, index + 4, index + 4, -1, index + 4, NULL, NULL, NULL, 0); win_register_button_func(btn, draw_loc_on, draw_loc_off, NULL, NULL); draw_loc(index + 4, colorTable[992]); } win_draw(call_win); bool gameUiWasDisabled = game_ui_is_disabled(); if (gameUiWasDisabled) { game_ui_enable(); } gmouse_disable(0); gmouse_set_cursor(MOUSE_CURSOR_ARROW); int eventCode; while (true) { eventCode = get_input(); if (eventCode == KEY_ESCAPE) { break; } if (eventCode >= 0 && eventCode < HIT_LOCATION_COUNT) { break; } if (game_user_wants_to_quit != 0) { break; } } gmouse_enable(); if (gameUiWasDisabled) { game_ui_disable(0); } text_font(oldFont); art_ptr_unlock(downHandle); art_ptr_unlock(upHandle); win_delete(call_win); if (eventCode == KEY_ESCAPE) { return -1; } *hitLocation = eventCode < 4 ? hit_loc_left[eventCode] : hit_loc_right[eventCode - 4]; gsound_play_sfx_file("icsxxxx1"); return 0; } // check for possibility of performing attacking // 0x426614 int combat_check_bad_shot(Object* attacker, Object* defender, int hitMode, bool aiming) { int range = 1; int tile = -1; if (defender != NULL) { tile = defender->tile; range = obj_dist(attacker, defender); if ((defender->data.critter.combat.results & DAM_DEAD) != 0) { return COMBAT_BAD_SHOT_ALREADY_DEAD; } } Object* weapon = item_hit_with(attacker, hitMode); if (weapon != NULL) { if ((attacker->data.critter.combat.results & DAM_CRIP_ARM_LEFT) != 0 && (attacker->data.critter.combat.results & DAM_CRIP_ARM_RIGHT) != 0) { return COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED; } if ((attacker->data.critter.combat.results & DAM_CRIP_ARM_ANY) != 0) { if (item_w_is_2handed(weapon)) { return COMBAT_BAD_SHOT_ARM_CRIPPLED; } } } if (item_w_mp_cost(attacker, hitMode, aiming) > attacker->data.critter.combat.ap) { return COMBAT_BAD_SHOT_NOT_ENOUGH_AP; } if (item_w_range(attacker, hitMode) < range) { return COMBAT_BAD_SHOT_OUT_OF_RANGE; } int attackType = item_w_subtype(weapon, hitMode); if (item_w_max_ammo(weapon) > 0) { if (item_w_curr_ammo(weapon) == 0) { return COMBAT_BAD_SHOT_NO_AMMO; } } if (attackType == ATTACK_TYPE_RANGED || attackType == ATTACK_TYPE_THROW || item_w_range(attacker, hitMode) > 1) { if (combat_is_shot_blocked(attacker, attacker->tile, tile, defender, NULL)) { return COMBAT_BAD_SHOT_AIM_BLOCKED; } } return COMBAT_BAD_SHOT_OK; } // 0x426744 bool combat_to_hit(Object* target, int* accuracy) { int hitMode; bool aiming; if (intface_get_attack(&hitMode, &aiming) == -1) { return false; } if (combat_check_bad_shot(obj_dude, target, hitMode, aiming) != COMBAT_BAD_SHOT_OK) { return false; } *accuracy = determine_to_hit_func(obj_dude, obj_dude->tile, target, HIT_LOCATION_UNCALLED, hitMode, 1); return true; } // 0x4267CC void combat_attack_this(Object* a1) { if (a1 == NULL) { return; } if ((combat_state & 0x02) == 0) { return; } int hitMode; bool aiming; if (intface_get_attack(&hitMode, &aiming) == -1) { return; } MessageListItem messageListItem; Object* item; char formattedText[80]; const char* sfx; int rc = combat_check_bad_shot(obj_dude, a1, hitMode, aiming); switch (rc) { case COMBAT_BAD_SHOT_NO_AMMO: item = item_hit_with(obj_dude, hitMode); messageListItem.num = 101; // Out of ammo. if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, item, hitMode, NULL); gsound_play_sfx_file(sfx); return; case COMBAT_BAD_SHOT_OUT_OF_RANGE: messageListItem.num = 102; // Target out of range. if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } return; case COMBAT_BAD_SHOT_NOT_ENOUGH_AP: item = item_hit_with(obj_dude, hitMode); messageListItem.num = 100; // You need %d action points. if (message_search(&combat_message_file, &messageListItem)) { int actionPointsRequired = item_w_mp_cost(obj_dude, hitMode, aiming); sprintf(formattedText, messageListItem.text, actionPointsRequired); display_print(formattedText); } return; case COMBAT_BAD_SHOT_ALREADY_DEAD: return; case COMBAT_BAD_SHOT_AIM_BLOCKED: messageListItem.num = 104; // Your aim is blocked. if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } return; case COMBAT_BAD_SHOT_ARM_CRIPPLED: messageListItem.num = 106; // You cannot use two-handed weapons with a crippled arm. if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } return; case COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED: messageListItem.num = 105; // You cannot use weapons with both arms crippled. if (message_search(&combat_message_file, &messageListItem)) { display_print(messageListItem.text); } return; } if (!isInCombat()) { STRUCT_664980 stru; stru.attacker = obj_dude; stru.defender = a1; stru.actionPointsBonus = 0; stru.accuracyBonus = 0; stru.damageBonus = 0; stru.minDamage = 0; stru.maxDamage = INT_MAX; stru.field_1C = 0; combat(&stru); return; } if (!aiming) { combat_attack(obj_dude, a1, hitMode, HIT_LOCATION_UNCALLED); return; } if (aiming != 1) { debug_printf("Bad called shot value %d\n", aiming); } int hitLocation; if (get_called_shot_location(a1, &hitLocation, hitMode) != -1) { combat_attack(obj_dude, a1, hitMode, hitLocation); } } // Highlights critters. // // 0x426AA8 void combat_outline_on() { int targetHighlight = TARGET_HIGHLIGHT_TARGETING_ONLY; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &targetHighlight); if (targetHighlight == TARGET_HIGHLIGHT_OFF) { return; } if (gmouse_3d_get_mode() != GAME_MOUSE_MODE_CROSSHAIR) { return; } if (isInCombat()) { for (int index = 0; index < list_total; index++) { combat_update_critter_outline_for_los(combat_list[index], 1); } } else { Object** critterList; int critterListLength = obj_create_list(-1, map_elevation, OBJ_TYPE_CRITTER, &critterList); for (int index = 0; index < critterListLength; index++) { Object* critter = critterList[index]; if (critter != obj_dude && (critter->data.critter.combat.results & DAM_DEAD) == 0) { combat_update_critter_outline_for_los(critter, 1); } } if (critterListLength != 0) { obj_delete_list(critterList); } } // NOTE: Uninline. combat_update_critters_in_los(1); tile_refresh_display(); } // 0x426BC0 void combat_outline_off() { int i; int v5; Object** v9; if (combat_state & 1) { for (i = 0; i < list_total; i++) { obj_turn_off_outline(combat_list[i], NULL); } } else { v5 = obj_create_list(-1, map_elevation, 1, &v9); for (i = 0; i < v5; i++) { obj_turn_off_outline(v9[i], NULL); obj_remove_outline(v9[i], NULL); } if (v5) { obj_delete_list(v9); } } tile_refresh_display(); } // 0x426C64 void combat_highlight_change() { int targetHighlight = 2; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &targetHighlight); if (targetHighlight != combat_highlight && isInCombat()) { if (targetHighlight != 0) { if (combat_highlight == 0) { combat_outline_on(); } } else { combat_outline_off(); } } combat_highlight = targetHighlight; } // Probably calculates line of sight or determines if object can see other object. // // 0x426CC4 bool combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5) { if (a5 != NULL) { *a5 = 0; } Object* obstacle = a1; int current = from; while (obstacle != NULL && current != to) { make_straight_path_func(a1, current, to, 0, &obstacle, 32, obj_shoot_blocking_at); if (obstacle != NULL) { if (FID_TYPE(obstacle->fid) != OBJ_TYPE_CRITTER && obstacle != a4) { return true; } if (a5 != NULL) { if (obstacle != a4) { if (a4 != NULL) { if ((a4->data.critter.combat.results & DAM_DEAD) == 0) { *a5 += 1; if ((a4->flags & OBJECT_MULTIHEX) != 0) { *a5 += 1; } } } } } if ((obstacle->flags & OBJECT_MULTIHEX) != 0) { int rotation = tile_dir(current, to); current = tile_num_in_direction(current, rotation, 1); } else { current = obstacle->tile; } } } return false; } // 0x426D94 int combat_player_knocked_out_by() { if ((obj_dude->data.critter.combat.results & DAM_DEAD) != 0) { return -1; } if (combat_ending_guy == NULL) { return -1; } return combat_ending_guy->data.critter.combat.team; } // 0x426DB8 int combat_explode_scenery(Object* a1, Object* a2) { scr_explode_scenery(a1, a1->tile, item_w_rocket_dmg_radius(NULL), a1->elevation); return 0; } // 0x426DDC void combat_delete_critter(Object* obj) { // TODO: Check entire function. if (!isInCombat()) { return; } if (list_total == 0) { return; } int i; for (i = 0; i < list_total; i++) { if (obj == combat_list[i]) { break; } } if (i == list_total) { return; } while (i < (list_total - 1)) { combat_list[i] = combat_list[i + 1]; combatCopyAIInfo(i + 1, i); i++; } list_total--; combat_list[list_total] = obj; if (i >= list_com) { if (i < (list_noncom + list_com)) { list_noncom--; } } else { list_com--; } obj->data.critter.combat.ap = 0; obj_remove_outline(obj, NULL); obj->data.critter.combat.whoHitMe = NULL; combatai_delete_critter(obj); } // 0x426EC4 void combatKillCritterOutsideCombat(Object* critter_obj, char* msg) { if (critter_obj != obj_dude) { display_print(msg); exec_script_proc(critter_obj->sid, SCRIPT_PROC_DESTROY); critter_kill(critter_obj, -1, 1); } } ================================================ FILE: src/game/combat.h ================================================ #ifndef FALLOUT_GAME_COMBAT_H_ #define FALLOUT_GAME_COMBAT_H_ #include "game/anim.h" #include "game/combat_defs.h" #include "plib/db/db.h" #include "game/message.h" #include "game/object_types.h" #include "game/party.h" #include "game/proto_types.h" extern int combatNumTurns; extern unsigned int combat_state; extern STRUCT_664980* gcsd; extern bool combat_call_display; extern int cf_table[WEAPON_CRITICAL_FAILURE_TYPE_COUNT][WEAPON_CRITICAL_FAILURE_EFFECT_COUNT]; extern MessageList combat_message_file; extern Object* combat_turn_obj; extern int combat_exps; extern int combat_free_move; int combat_init(); void combat_reset(); void combat_exit(); int find_cid(int a1, int a2, Object** a3, int a4); int combat_load(File* stream); int combat_save(File* stream); bool combat_safety_invalidate_weapon(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr); bool combat_safety_invalidate_weapon_func(Object* attacker, Object* weapon, int hitMode, Object* defender, int* safeDistancePtr, Object* attackerFriend); bool combatTestIncidentalHit(Object* attacker, Object* defender, Object* attackerFriend, Object* weapon); Object* combat_whose_turn(); void combat_data_init(Object* obj); Object* combatAIInfoGetFriendlyDead(Object* obj); int combatAIInfoSetFriendlyDead(Object* a1, Object* a2); Object* combatAIInfoGetLastTarget(Object* obj); int combatAIInfoSetLastTarget(Object* a1, Object* a2); Object* combatAIInfoGetLastItem(Object* obj); int combatAIInfoSetLastItem(Object* obj, Object* a2); int combatAIInfoGetLastMove(Object* object); int combatAIInfoSetLastMove(Object* object, int move); void combat_update_critter_outline_for_los(Object* critter, bool a2); void combat_over_from_load(); void combat_give_exps(int exp_points); int combat_in_range(Object* critter); void combat_end(); void combat_turn_run(); void combat_end_turn(); void combat(STRUCT_664980* attack); void combat_ctd_init(Attack* attack, Object* a2, Object* a3, int a4, int a5); int combat_attack(Object* a1, Object* a2, int a3, int a4); int combat_bullet_start(const Object* a1, const Object* a2); void compute_explosion_on_extras(Attack* attack, int a2, bool isGrenade, int a4); int determine_to_hit(Object* a1, Object* a2, int hitLocation, int hitMode); int determine_to_hit_no_range(Object* a1, Object* a2, int a3, int a4, unsigned char* a5); int determine_to_hit_from_tile(Object* a1, int a2, Object* a3, int a4, int a5); void death_checks(Attack* attack); void apply_damage(Attack* attack, bool animated); void combat_display(Attack* attack); void combat_anim_begin(); void combat_anim_finished(); int combat_check_bad_shot(Object* attacker, Object* defender, int hitMode, bool aiming); bool combat_to_hit(Object* target, int* accuracy); void combat_attack_this(Object* a1); void combat_outline_on(); void combat_outline_off(); void combat_highlight_change(); bool combat_is_shot_blocked(Object* a1, int from, int to, Object* a4, int* a5); int combat_player_knocked_out_by(); int combat_explode_scenery(Object* a1, Object* a2); void combat_delete_critter(Object* obj); void combatKillCritterOutsideCombat(Object* critter_obj, char* msg); static inline bool isInCombat() { return (combat_state & COMBAT_STATE_0x01) != 0; } #endif /* FALLOUT_GAME_COMBAT_H_ */ ================================================ FILE: src/game/combat_defs.h ================================================ #ifndef FALLOUT_GAME_COMBAT_DEFS_H_ #define FALLOUT_GAME_COMBAT_DEFS_H_ #include "game/object_types.h" #define EXPLOSION_TARGET_COUNT (6) #define CRTICIAL_EFFECT_COUNT (6) #define WEAPON_CRITICAL_FAILURE_TYPE_COUNT (7) #define WEAPON_CRITICAL_FAILURE_EFFECT_COUNT (5) typedef enum CombatState { COMBAT_STATE_0x01 = 0x01, COMBAT_STATE_0x02 = 0x02, COMBAT_STATE_0x08 = 0x08, } CombatState; typedef enum HitMode { HIT_MODE_LEFT_WEAPON_PRIMARY = 0, HIT_MODE_LEFT_WEAPON_SECONDARY = 1, HIT_MODE_RIGHT_WEAPON_PRIMARY = 2, HIT_MODE_RIGHT_WEAPON_SECONDARY = 3, HIT_MODE_PUNCH = 4, HIT_MODE_KICK = 5, HIT_MODE_LEFT_WEAPON_RELOAD = 6, HIT_MODE_RIGHT_WEAPON_RELOAD = 7, // Punch Level 2 HIT_MODE_STRONG_PUNCH = 8, // Punch Level 3 HIT_MODE_HAMMER_PUNCH = 9, // Punch Level 4 aka 'Lightning Punch' HIT_MODE_HAYMAKER = 10, // Punch Level 5 aka 'Chop Punch' HIT_MODE_JAB = 11, // Punch Level 6 aka 'Dragon Punch' HIT_MODE_PALM_STRIKE = 12, // Punch Level 7 aka 'Force Punch' HIT_MODE_PIERCING_STRIKE = 13, // Kick Level 2 HIT_MODE_STRONG_KICK = 14, // Kick Level 3 HIT_MODE_SNAP_KICK = 15, // Kick Level 4 aka 'Roundhouse Kick' HIT_MODE_POWER_KICK = 16, // Kick Level 5 HIT_MODE_HIP_KICK = 17, // Kick Level 6 aka 'Jump Kick' HIT_MODE_HOOK_KICK = 18, // Kick Level 7 aka 'Death Blossom Kick' HIT_MODE_PIERCING_KICK = 19, HIT_MODE_COUNT, FIRST_ADVANCED_PUNCH_HIT_MODE = HIT_MODE_STRONG_PUNCH, LAST_ADVANCED_PUNCH_HIT_MODE = HIT_MODE_PIERCING_STRIKE, FIRST_ADVANCED_KICK_HIT_MODE = HIT_MODE_STRONG_KICK, LAST_ADVANCED_KICK_HIT_MODE = HIT_MODE_PIERCING_KICK, FIRST_ADVANCED_UNARMED_HIT_MODE = FIRST_ADVANCED_PUNCH_HIT_MODE, LAST_ADVANCED_UNARMED_HIT_MODE = LAST_ADVANCED_KICK_HIT_MODE, } HitMode; typedef enum HitLocation { HIT_LOCATION_HEAD, HIT_LOCATION_LEFT_ARM, HIT_LOCATION_RIGHT_ARM, HIT_LOCATION_TORSO, HIT_LOCATION_RIGHT_LEG, HIT_LOCATION_LEFT_LEG, HIT_LOCATION_EYES, HIT_LOCATION_GROIN, HIT_LOCATION_UNCALLED, HIT_LOCATION_COUNT, HIT_LOCATION_SPECIFIC_COUNT = HIT_LOCATION_COUNT - 1, } HitLocation; typedef struct CombatAiInfo { Object* friendlyDead; Object* lastTarget; Object* lastItem; int lastMove; } CombatAiInfo; typedef struct STRUCT_664980 { Object* attacker; Object* defender; int actionPointsBonus; int accuracyBonus; int damageBonus; int minDamage; int maxDamage; int field_1C; // probably bool, indicating field_20 and field_24 used int field_20; // flags on attacker int field_24; // flags on defender } STRUCT_664980; static_assert(sizeof(STRUCT_664980) == 40, "wrong size"); typedef struct Attack { Object* attacker; int hitMode; Object* weapon; int attackHitLocation; int attackerDamage; int attackerFlags; int ammoQuantity; int criticalMessageId; Object* defender; int tile; int defenderHitLocation; int defenderDamage; int defenderFlags; int defenderKnockback; Object* oops; int extrasLength; Object* extras[EXPLOSION_TARGET_COUNT]; int extrasHitLocation[EXPLOSION_TARGET_COUNT]; int extrasDamage[EXPLOSION_TARGET_COUNT]; int extrasFlags[EXPLOSION_TARGET_COUNT]; int extrasKnockback[EXPLOSION_TARGET_COUNT]; } Attack; static_assert(sizeof(Attack) == 184, "wrong size"); // Provides metadata about critical hit effect. typedef struct CriticalHitDescription { int damageMultiplier; // Damage flags that will be applied to defender. int flags; // Stat to check to upgrade this critical hit to massive critical hit or // -1 if there is no massive critical hit. int massiveCriticalStat; // Bonus/penalty to massive critical stat. int massiveCriticalStatModifier; // Additional damage flags if this critical hit become massive critical. int massiveCriticalFlags; int messageId; int massiveCriticalMessageId; } CriticalHitDescription; typedef enum CombatBadShot { COMBAT_BAD_SHOT_OK = 0, COMBAT_BAD_SHOT_NO_AMMO = 1, COMBAT_BAD_SHOT_OUT_OF_RANGE = 2, COMBAT_BAD_SHOT_NOT_ENOUGH_AP = 3, COMBAT_BAD_SHOT_ALREADY_DEAD = 4, COMBAT_BAD_SHOT_AIM_BLOCKED = 5, COMBAT_BAD_SHOT_ARM_CRIPPLED = 6, COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED = 7, } CombatBadShot; #endif /* FALLOUT_GAME_COMBAT_DEFS_H_ */ ================================================ FILE: src/game/combatai.c ================================================ #include "game/combatai.h" #include #include #include #include "game/actions.h" #include "game/anim.h" #include "game/combat.h" #include "game/config.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/light.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/proto.h" #include "game/protinst.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/textobj.h" #include "game/tile.h" static void parse_hurt_str(char* str, int* out_value); static int cai_match_str_to_list(const char* str, const char** list, int count, int* out_value); static void cai_init_cap(AiPacket* ai); static int cai_cap_load(File* stream, AiPacket* ai); static int cai_cap_save(File* stream, AiPacket* ai); static AiPacket* ai_cap(Object* obj); static AiPacket* ai_cap_from_packet(int aiPacketNum); static int ai_magic_hands(Object* a1, Object* a2, int num); static int ai_check_drugs(Object* critter); static void ai_run_away(Object* a1, Object* a2); static int ai_move_away(Object* a1, Object* a2, int a3); static bool ai_find_friend(Object* a1, int a2, int a3); static int compare_nearer(const void* a1, const void* a2); static void ai_sort_list_distance(Object** critterList, int length, Object* origin); static void ai_sort_list_strength(Object** critterList, int length); static void ai_sort_list_weakness(Object** critterList, int length); static Object* ai_find_nearest_team(Object* a1, Object* a2, int a3); static Object* ai_find_nearest_team_in_combat(Object* a1, Object* a2, int a3); static int ai_find_attackers(Object* a1, Object** a2, Object** a3, Object** a4); static int ai_have_ammo(Object* critter_obj, Object* weapon_obj, Object** out_ammo_obj); static bool caiHasWeapPrefType(AiPacket* ai, int attackType); static Object* ai_best_weapon(Object* a1, Object* a2, Object* a3, Object* a4); static bool ai_can_use_weapon(Object* critter, Object* weapon, int hitMode); static bool ai_can_use_drug(Object* obj, Object* a2); static Object* ai_search_environ(Object* critter, int itemType); static Object* ai_retrieve_object(Object* a1, Object* a2); static int ai_pick_hit_mode(Object* a1, Object* a2, Object* a3); static int ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, int a4); static int ai_move_closer(Object* a1, Object* a2, int a3); static int cai_retargetTileFromFriendlyFire(Object* source, Object* target, int* tilePtr); static int cai_retargetTileFromFriendlyFireSubFunc(AiRetargetData* aiRetargetData, int tile); static bool cai_attackWouldIntersect(Object* attacker, Object* defender, Object* attackerFriend, int tile, int* distance); static int ai_switch_weapons(Object* a1, int* hitMode, Object** weapon, Object* a4); static int ai_called_shot(Object* a1, Object* a2, int a3); static int ai_attack(Object* a1, Object* a2, int a3); static int ai_try_attack(Object* a1, Object* a2); static int cai_perform_distance_prefs(Object* a1, Object* a2); static int cai_get_min_hp(AiPacket* ai); static int ai_print_msg(Object* critter, int type); static int combatai_rating(Object* obj); static int combatai_load_messages(); static int combatai_unload_messages(); // 0x51805C static Object* combat_obj = NULL; // 0x518060 static int num_caps = 0; // 0x518064 static AiPacket* cap = NULL; // 0x518068 static bool combatai_is_initialized = false; // 0x51806C const char* area_attack_mode_strs[AREA_ATTACK_MODE_COUNT] = { "always", "sometimes", "be_sure", "be_careful", "be_absolutely_sure", }; // 0x5180D0 const char* attack_who_mode_strs[ATTACK_WHO_COUNT] = { "whomever_attacking_me", "strongest", "weakest", "whomever", "closest", }; // 0x51809C const char* weapon_pref_strs[BEST_WEAPON_COUNT] = { "no_pref", "melee", "melee_over_ranged", "ranged_over_melee", "ranged", "unarmed", "unarmed_over_thrown", "random", }; // 0x5180E4 const char* chem_use_mode_strs[CHEM_USE_COUNT] = { "clean", "stims_when_hurt_little", "stims_when_hurt_lots", "sometimes", "anytime", "always", }; // 0x5180BC const char* distance_pref_strs[DISTANCE_COUNT] = { "stay_close", "charge", "snipe", "on_your_own", "stay", }; // 0x518080 const char* run_away_mode_strs[RUN_AWAY_MODE_COUNT] = { "none", "coward", "finger_hurts", "bleeding", "not_feeling_good", "tourniquet", "never", }; // 0x5180FC const char* disposition_strs[DISPOSITION_COUNT] = { "none", "custom", "coward", "defensive", "aggressive", "berserk", }; // 0x518114 static const char* matchHurtStrs[HURT_COUNT] = { "blind", "crippled", "crippled_legs", "crippled_arms", }; // hurt_too_much // // 0x518124 static int rmatchHurtVals[5] = { DAM_BLIND, DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT | DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT, DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT, DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT, 0, }; // Hit points in percent to choose run away mode. // // 0x518138 static int runModeValues[6] = { 0, 25, 40, 60, 75, 100, }; // 0x518150 static Object* attackerTeamObj = NULL; // 0x518154 static Object* targetTeamObj = NULL; // 0x518158 static int weapPrefOrderings[BEST_WEAPON_COUNT + 1][5] = { { ATTACK_TYPE_RANGED, ATTACK_TYPE_THROW, ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED, 0 }, { ATTACK_TYPE_RANGED, ATTACK_TYPE_THROW, ATTACK_TYPE_MELEE, ATTACK_TYPE_UNARMED, 0 }, // BEST_WEAPON_NO_PREF { ATTACK_TYPE_MELEE, 0, 0, 0, 0 }, // BEST_WEAPON_MELEE { ATTACK_TYPE_MELEE, ATTACK_TYPE_RANGED, 0, 0, 0 }, // BEST_WEAPON_MELEE_OVER_RANGED { ATTACK_TYPE_RANGED, ATTACK_TYPE_MELEE, 0, 0, 0 }, // BEST_WEAPON_RANGED_OVER_MELEE { ATTACK_TYPE_RANGED, 0, 0, 0, 0 }, // BEST_WEAPON_RANGED { ATTACK_TYPE_UNARMED, 0, 0, 0, 0 }, // BEST_WEAPON_UNARMED { ATTACK_TYPE_UNARMED, ATTACK_TYPE_THROW, 0, 0, 0 }, // BEST_WEAPON_UNARMED_OVER_THROW { 0, 0, 0, 0, 0 }, // BEST_WEAPON_RANDOM }; // ai.msg // // 0x56D510 static MessageList ai_message_file; // 0x56D518 static char target_str[260]; // 0x56D61C static int curr_crit_num; // 0x56D620 static Object** curr_crit_list; // 0x56D624 static char attack_str[268]; // parse hurt_too_much static void parse_hurt_str(char* str, int* valuePtr) { int v5, v10; char tmp; int i; *valuePtr = 0; str = strlwr(str); while (*str) { v5 = strspn(str, " "); str += v5; v10 = strcspn(str, ","); tmp = str[v10]; str[v10] = '\0'; for (i = 0; i < 4; i++) { if (strcmp(str, matchHurtStrs[i]) == 0) { *valuePtr |= rmatchHurtVals[i]; break; } } if (i == 4) { debug_printf("Unrecognized flag: %s\n", str); } str[v10] = tmp; if (tmp == '\0') { break; } str += v10 + 1; } } // parse behaviour entry static int cai_match_str_to_list(const char* str, const char** list, int count, int* valuePtr) { *valuePtr = -1; for (int index = 0; index < count; index++) { if (stricmp(str, list[index]) == 0) { *valuePtr = index; } } return 0; } // 0x426FE0 static void cai_init_cap(AiPacket* ai) { ai->name = NULL; ai->area_attack_mode = -1; ai->run_away_mode = -1; ai->best_weapon = -1; ai->distance = -1; ai->attack_who = -1; ai->chem_use = -1; for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { ai->chem_primary_desire[index] = -1; } ai->disposition = -1; } // ai_init // 0x42703C int combat_ai_init() { int index; if (combatai_load_messages() == -1) { return -1; } num_caps = 0; Config config; if (!config_init(&config)) { return -1; } if (!config_load(&config, "data\\ai.txt", true)) { return -1; } cap = (AiPacket*)mem_malloc(sizeof(*cap) * config.size); if (cap == NULL) { goto err; } for (index = 0; index < config.size; index++) { cai_init_cap(&(cap[index])); } for (index = 0; index < config.size; index++) { assoc_pair* sectionEntry = &(config.list[index]); AiPacket* ai = &(cap[index]); char* stringValue; ai->name = mem_strdup(sectionEntry->name); if (!config_get_value(&config, sectionEntry->name, "packet_num", &(ai->packet_num))) goto err; if (!config_get_value(&config, sectionEntry->name, "max_dist", &(ai->max_dist))) goto err; if (!config_get_value(&config, sectionEntry->name, "min_to_hit", &(ai->min_to_hit))) goto err; if (!config_get_value(&config, sectionEntry->name, "min_hp", &(ai->min_hp))) goto err; if (!config_get_value(&config, sectionEntry->name, "aggression", &(ai->aggression))) goto err; if (config_get_string(&config, sectionEntry->name, "hurt_too_much", &stringValue)) { parse_hurt_str(stringValue, &(ai->hurt_too_much)); } if (!config_get_value(&config, sectionEntry->name, "secondary_freq", &(ai->secondary_freq))) goto err; if (!config_get_value(&config, sectionEntry->name, "called_freq", &(ai->called_freq))) goto err; if (!config_get_value(&config, sectionEntry->name, "font", &(ai->font))) goto err; if (!config_get_value(&config, sectionEntry->name, "color", &(ai->color))) goto err; if (!config_get_value(&config, sectionEntry->name, "outline_color", &(ai->outline_color))) goto err; if (!config_get_value(&config, sectionEntry->name, "chance", &(ai->chance))) goto err; if (!config_get_value(&config, sectionEntry->name, "run_start", &(ai->run.start))) goto err; if (!config_get_value(&config, sectionEntry->name, "run_end", &(ai->run.end))) goto err; if (!config_get_value(&config, sectionEntry->name, "move_start", &(ai->move.start))) goto err; if (!config_get_value(&config, sectionEntry->name, "move_end", &(ai->move.end))) goto err; if (!config_get_value(&config, sectionEntry->name, "attack_start", &(ai->attack.start))) goto err; if (!config_get_value(&config, sectionEntry->name, "attack_end", &(ai->attack.end))) goto err; if (!config_get_value(&config, sectionEntry->name, "miss_start", &(ai->miss.start))) goto err; if (!config_get_value(&config, sectionEntry->name, "miss_end", &(ai->miss.end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_head_start", &(ai->hit[HIT_LOCATION_HEAD].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_head_end", &(ai->hit[HIT_LOCATION_HEAD].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_left_arm_start", &(ai->hit[HIT_LOCATION_LEFT_ARM].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_left_arm_end", &(ai->hit[HIT_LOCATION_LEFT_ARM].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_right_arm_start", &(ai->hit[HIT_LOCATION_RIGHT_ARM].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_right_arm_end", &(ai->hit[HIT_LOCATION_RIGHT_ARM].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_torso_start", &(ai->hit[HIT_LOCATION_TORSO].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_torso_end", &(ai->hit[HIT_LOCATION_TORSO].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_right_leg_start", &(ai->hit[HIT_LOCATION_RIGHT_LEG].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_right_leg_end", &(ai->hit[HIT_LOCATION_RIGHT_LEG].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_left_leg_start", &(ai->hit[HIT_LOCATION_LEFT_LEG].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_left_leg_end", &(ai->hit[HIT_LOCATION_LEFT_LEG].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_eyes_start", &(ai->hit[HIT_LOCATION_EYES].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_eyes_end", &(ai->hit[HIT_LOCATION_EYES].end))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_groin_start", &(ai->hit[HIT_LOCATION_GROIN].start))) goto err; if (!config_get_value(&config, sectionEntry->name, "hit_groin_end", &(ai->hit[HIT_LOCATION_GROIN].end))) goto err; ai->hit[HIT_LOCATION_GROIN].end++; if (config_get_string(&config, sectionEntry->name, "area_attack_mode", &stringValue)) { cai_match_str_to_list(stringValue, area_attack_mode_strs, AREA_ATTACK_MODE_COUNT, &(ai->area_attack_mode)); } else { ai->run_away_mode = -1; } if (config_get_string(&config, sectionEntry->name, "run_away_mode", &stringValue)) { cai_match_str_to_list(stringValue, run_away_mode_strs, RUN_AWAY_MODE_COUNT, &(ai->run_away_mode)); if (ai->run_away_mode >= 0) { ai->run_away_mode--; } } if (config_get_string(&config, sectionEntry->name, "best_weapon", &stringValue)) { cai_match_str_to_list(stringValue, weapon_pref_strs, BEST_WEAPON_COUNT, &(ai->best_weapon)); } if (config_get_string(&config, sectionEntry->name, "distance", &stringValue)) { cai_match_str_to_list(stringValue, distance_pref_strs, DISTANCE_COUNT, &(ai->distance)); } if (config_get_string(&config, sectionEntry->name, "attack_who", &stringValue)) { cai_match_str_to_list(stringValue, attack_who_mode_strs, ATTACK_WHO_COUNT, &(ai->attack_who)); } if (config_get_string(&config, sectionEntry->name, "chem_use", &stringValue)) { cai_match_str_to_list(stringValue, chem_use_mode_strs, CHEM_USE_COUNT, &(ai->chem_use)); } config_get_values(&config, sectionEntry->name, "chem_primary_desire", ai->chem_primary_desire, AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT); if (config_get_string(&config, sectionEntry->name, "disposition", &stringValue)) { cai_match_str_to_list(stringValue, disposition_strs, DISPOSITION_COUNT, &(ai->disposition)); ai->disposition--; } if (config_get_string(&config, sectionEntry->name, "body_type", &stringValue)) { ai->body_type = mem_strdup(stringValue); } else { ai->body_type = NULL; } if (config_get_string(&config, sectionEntry->name, "general_type", &stringValue)) { ai->general_type = mem_strdup(stringValue); } else { ai->general_type = NULL; } } if (index < config.size) { goto err; } num_caps = config.size; config_exit(&config); combatai_is_initialized = true; return 0; err: if (cap != NULL) { for (index = 0; index < config.size; index++) { AiPacket* ai = &(cap[index]); if (ai->name != NULL) { mem_free(ai->name); } // FIXME: leaking ai->body_type and ai->general_type, does not matter // because it halts further processing } mem_free(cap); } debug_printf("Error processing ai.txt"); config_exit(&config); return -1; } // 0x4279F8 void combat_ai_reset() { } // 0x4279FC int combat_ai_exit() { for (int index = 0; index < num_caps; index++) { AiPacket* ai = &(cap[index]); if (ai->name != NULL) { mem_free(ai->name); ai->name = NULL; } if (ai->general_type != NULL) { mem_free(ai->general_type); ai->general_type = NULL; } if (ai->body_type != NULL) { mem_free(ai->body_type); ai->body_type = NULL; } } mem_free(cap); num_caps = 0; combatai_is_initialized = false; // NOTE: Uninline. if (combatai_unload_messages() != 0) { return -1; } return 0; } // 0x427AD8 int combat_ai_load(File* stream) { for (int index = 0; index < partyMemberMaxCount; index++) { int pid = partyMemberPidList[index]; if (pid != -1 && PID_TYPE(pid) == OBJ_TYPE_CRITTER) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return -1; } AiPacket* ai = ai_cap_from_packet(proto->critter.aiPacket); if (ai->disposition == 0) { cai_cap_load(stream, ai); } } } return 0; } // 0x427B50 int combat_ai_save(File* stream) { for (int index = 0; index < partyMemberMaxCount; index++) { int pid = partyMemberPidList[index]; if (pid != -1 && PID_TYPE(pid) == OBJ_TYPE_CRITTER) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return -1; } AiPacket* ai = ai_cap_from_packet(proto->critter.aiPacket); if (ai->disposition == 0) { cai_cap_save(stream, ai); } } } return 0; } // 0x427BC8 static int cai_cap_load(File* stream, AiPacket* ai) { if (db_freadInt(stream, &(ai->packet_num)) == -1) return -1; if (db_freadInt(stream, &(ai->max_dist)) == -1) return -1; if (db_freadInt(stream, &(ai->min_to_hit)) == -1) return -1; if (db_freadInt(stream, &(ai->min_hp)) == -1) return -1; if (db_freadInt(stream, &(ai->aggression)) == -1) return -1; if (db_freadInt(stream, &(ai->hurt_too_much)) == -1) return -1; if (db_freadInt(stream, &(ai->secondary_freq)) == -1) return -1; if (db_freadInt(stream, &(ai->called_freq)) == -1) return -1; if (db_freadInt(stream, &(ai->font)) == -1) return -1; if (db_freadInt(stream, &(ai->color)) == -1) return -1; if (db_freadInt(stream, &(ai->outline_color)) == -1) return -1; if (db_freadInt(stream, &(ai->chance)) == -1) return -1; if (db_freadInt(stream, &(ai->run.start)) == -1) return -1; if (db_freadInt(stream, &(ai->run.end)) == -1) return -1; if (db_freadInt(stream, &(ai->move.start)) == -1) return -1; if (db_freadInt(stream, &(ai->move.end)) == -1) return -1; if (db_freadInt(stream, &(ai->attack.start)) == -1) return -1; if (db_freadInt(stream, &(ai->attack.end)) == -1) return -1; if (db_freadInt(stream, &(ai->miss.start)) == -1) return -1; if (db_freadInt(stream, &(ai->miss.end)) == -1) return -1; for (int index = 0; index < HIT_LOCATION_SPECIFIC_COUNT; index++) { AiMessageRange* range = &(ai->hit[index]); if (db_freadInt(stream, &(range->start)) == -1) return -1; if (db_freadInt(stream, &(range->end)) == -1) return -1; } if (db_freadInt(stream, &(ai->area_attack_mode)) == -1) return -1; if (db_freadInt(stream, &(ai->best_weapon)) == -1) return -1; if (db_freadInt(stream, &(ai->distance)) == -1) return -1; if (db_freadInt(stream, &(ai->attack_who)) == -1) return -1; if (db_freadInt(stream, &(ai->chem_use)) == -1) return -1; if (db_freadInt(stream, &(ai->run_away_mode)) == -1) return -1; for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { if (db_freadInt(stream, &(ai->chem_primary_desire[index])) == -1) return -1; } return 0; } // 0x427E1C static int cai_cap_save(File* stream, AiPacket* ai) { if (db_fwriteInt(stream, ai->packet_num) == -1) return -1; if (db_fwriteInt(stream, ai->max_dist) == -1) return -1; if (db_fwriteInt(stream, ai->min_to_hit) == -1) return -1; if (db_fwriteInt(stream, ai->min_hp) == -1) return -1; if (db_fwriteInt(stream, ai->aggression) == -1) return -1; if (db_fwriteInt(stream, ai->hurt_too_much) == -1) return -1; if (db_fwriteInt(stream, ai->secondary_freq) == -1) return -1; if (db_fwriteInt(stream, ai->called_freq) == -1) return -1; if (db_fwriteInt(stream, ai->font) == -1) return -1; if (db_fwriteInt(stream, ai->color) == -1) return -1; if (db_fwriteInt(stream, ai->outline_color) == -1) return -1; if (db_fwriteInt(stream, ai->chance) == -1) return -1; if (db_fwriteInt(stream, ai->run.start) == -1) return -1; if (db_fwriteInt(stream, ai->run.end) == -1) return -1; if (db_fwriteInt(stream, ai->move.start) == -1) return -1; if (db_fwriteInt(stream, ai->move.end) == -1) return -1; if (db_fwriteInt(stream, ai->attack.start) == -1) return -1; if (db_fwriteInt(stream, ai->attack.end) == -1) return -1; if (db_fwriteInt(stream, ai->miss.start) == -1) return -1; if (db_fwriteInt(stream, ai->miss.end) == -1) return -1; for (int index = 0; index < HIT_LOCATION_SPECIFIC_COUNT; index++) { AiMessageRange* range = &(ai->hit[index]); if (db_fwriteInt(stream, range->start) == -1) return -1; if (db_fwriteInt(stream, range->end) == -1) return -1; } if (db_fwriteInt(stream, ai->area_attack_mode) == -1) return -1; if (db_fwriteInt(stream, ai->best_weapon) == -1) return -1; if (db_fwriteInt(stream, ai->distance) == -1) return -1; if (db_fwriteInt(stream, ai->attack_who) == -1) return -1; if (db_fwriteInt(stream, ai->chem_use) == -1) return -1; if (db_fwriteInt(stream, ai->run_away_mode) == -1) return -1; for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { // TODO: Check, probably writes chem_primary_desire[0] three times, // might be a bug in original source code. if (db_fwriteInt(stream, ai->chem_primary_desire[index]) == -1) return -1; } return 0; } // NOTE: Unused. // // 0x428058 int combat_ai_num() { return num_caps; } // 0x428060 char* combat_ai_name(int packetNum) { int index; if (packetNum < 0 || packetNum >= num_caps) { return NULL; } for (index = 0; index < num_caps; index++) { if (cap[index].packet_num == packetNum) { return cap[index].name; } } return NULL; } // Get ai from object // // 0x4280B4 static AiPacket* ai_cap(Object* obj) { // NOTE: Uninline. AiPacket* ai = ai_cap_from_packet(obj->data.critter.combat.aiPacket); return ai; } // get ai packet by num // // 0x42811C static AiPacket* ai_cap_from_packet(int aiPacketId) { for (int index = 0; index < num_caps; index++) { AiPacket* ai = &(cap[index]); if (aiPacketId == ai->packet_num) { return ai; } } debug_printf("Missing AI Packet\n"); return cap; } // 0x428184 int ai_get_burst_value(Object* obj) { AiPacket* ai = ai_cap(obj); return ai->area_attack_mode; } // 0x428190 int ai_get_run_away_value(Object* obj) { AiPacket* ai; int v3; int v5; int v6; int i; ai = ai_cap(obj); v3 = -1; if (ai->run_away_mode != -1) { return ai->run_away_mode; } v5 = 100 * ai->min_hp; v6 = v5 / critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS); for (i = 0; i < 6; i++) { if (v6 >= runModeValues[i]) { v3 = i; } } return v3; } // 0x4281FC int ai_get_weapon_pref_value(Object* obj) { AiPacket* ai = ai_cap(obj); return ai->best_weapon; } // 0x428208 int ai_get_distance_pref_value(Object* obj) { AiPacket* ai = ai_cap(obj); return ai->distance; } // 0x428214 int ai_get_attack_who_value(Object* obj) { AiPacket* ai = ai_cap(obj); return ai->attack_who; } // 0x428220 int ai_get_chem_use_value(Object* obj) { AiPacket* ai = ai_cap(obj); return ai->chem_use; } // 0x42822C int ai_set_burst_value(Object* critter, int areaAttackMode) { if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) { return -1; } AiPacket* ai = ai_cap(critter); ai->area_attack_mode = areaAttackMode; return 0; } // 0x428248 int ai_set_run_away_value(Object* obj, int runAwayMode) { if (runAwayMode >= 6) { return -1; } AiPacket* ai = ai_cap(obj); ai->run_away_mode = runAwayMode; int maximumHp = critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS); ai->min_hp = maximumHp - maximumHp * runModeValues[runAwayMode] / 100; int currentHp = critterGetStat(obj, STAT_CURRENT_HIT_POINTS); const char* name = critter_name(obj); debug_printf("\n%s minHp = %d; curHp = %d", name, ai->min_hp, currentHp); return 0; } // 0x4282D0 int ai_set_weapon_pref_value(Object* critter, int bestWeapon) { if (bestWeapon >= BEST_WEAPON_COUNT) { return -1; } AiPacket* ai = ai_cap(critter); ai->best_weapon = bestWeapon; return 0; } // 0x4282EC int ai_set_distance_pref_value(Object* critter, int distance) { if (distance >= DISTANCE_COUNT) { return -1; } AiPacket* ai = ai_cap(critter); ai->distance = distance; return 0; } // 0x428308 int ai_set_attack_who_value(Object* critter, int attackWho) { if (attackWho >= ATTACK_WHO_COUNT) { return -1; } AiPacket* ai = ai_cap(critter); ai->attack_who = attackWho; return 0; } // 0x428324 int ai_set_chem_use_value(Object* critter, int chemUse) { if (chemUse >= CHEM_USE_COUNT) { return -1; } AiPacket* ai = ai_cap(critter); ai->chem_use = chemUse; return 0; } // 0x428340 int ai_get_disposition(Object* obj) { if (obj == NULL) { return 0; } AiPacket* ai = ai_cap(obj); return ai->disposition; } // 0x428354 int ai_set_disposition(Object* obj, int disposition) { if (obj == NULL) { return -1; } if (disposition == -1 || disposition >= 5) { return -1; } AiPacket* ai = ai_cap(obj); obj->data.critter.combat.aiPacket = ai->packet_num - (disposition - ai->disposition); return 0; } // 0x428398 static int ai_magic_hands(Object* critter, Object* item, int num) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_animate(critter, ANIM_MAGIC_HANDS_MIDDLE, 0); if (register_end() == 0) { if (isInCombat()) { combat_turn_run(); } } if (num != -1) { MessageListItem messageListItem; messageListItem.num = num; if (message_search(&misc_message_file, &messageListItem)) { const char* critterName = object_name(critter); char text[200]; if (item != NULL) { const char* itemName = object_name(item); sprintf(text, "%s %s %s.", critterName, messageListItem.text, itemName); } else { sprintf(text, "%s %s.", critterName, messageListItem.text); } display_print(text); } } return 0; } // ai using drugs // 0x428480 static int ai_check_drugs(Object* critter) { if (critter_body_type(critter) != BODY_TYPE_BIPED) { return 0; } int v25 = 0; int v28 = 0; int v29 = 0; Object* v3 = combatAIInfoGetLastItem(critter); if (v3 == NULL) { AiPacket* ai = ai_cap(critter); if (ai == NULL) { return 0; } int v2 = 50; int v26 = 0; switch (ai->chem_use + 1) { case 1: return 0; case 2: v2 = 60; break; case 3: v2 = 30; break; case 4: if ((combatNumTurns % 3) == 0) { v26 = 25; } v2 = 50; break; case 5: if ((combatNumTurns % 3) == 0) { v26 = 75; } v2 = 50; break; case 6: v26 = 100; break; } int v27 = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS) * v2 / 100; int token = -1; while (true) { if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) >= v27 || critter->data.critter.combat.ap < 2) { break; } Object* drug = inven_find_type(critter, ITEM_TYPE_DRUG, &token); if (drug == NULL) { v25 = true; break; } int drugPid = drug->pid; if ((drugPid == PROTO_ID_STIMPACK || drugPid == PROTO_ID_SUPER_STIMPACK || drugPid == PROTO_ID_HEALING_POWDER) && item_remove_mult(critter, drug, 1) == 0) { if (item_d_take_drug(critter, drug) == -1) { item_add_force(critter, drug, 1); } else { ai_magic_hands(critter, drug, 5000); obj_connect(drug, critter->tile, critter->elevation, NULL); obj_destroy(drug); v28 = 1; } if (critter->data.critter.combat.ap < 2) { critter->data.critter.combat.ap = 0; } else { critter->data.critter.combat.ap -= 2; } token = -1; } } if (!v28 && v26 > 0 && roll_random(0, 100) < v26) { while (critter->data.critter.combat.ap >= 2) { Object* drug = inven_find_type(critter, ITEM_TYPE_DRUG, &token); if (drug == NULL) { v25 = 1; break; } int drugPid = drug->pid; int index; for (index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { // TODO: Find out why it checks for inequality at 0x4286B1. if (ai->chem_primary_desire[index] != drugPid) { break; } } if (index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT) { if (drugPid != PROTO_ID_STIMPACK && drugPid != PROTO_ID_SUPER_STIMPACK && drugPid != 273 && item_remove_mult(critter, drug, 1) == 0) { if (item_d_take_drug(critter, drug) == -1) { item_add_force(critter, drug, 1); } else { ai_magic_hands(critter, drug, 5000); obj_connect(drug, critter->tile, critter->elevation, NULL); obj_destroy(drug); v28 = 1; v29 += 1; } if (critter->data.critter.combat.ap < 2) { critter->data.critter.combat.ap = 0; } else { critter->data.critter.combat.ap -= 2; } if (ai->chem_use == CHEM_USE_SOMETIMES || (ai->chem_use == CHEM_USE_ANYTIME && v29 >= 2)) { break; } } } } } } if (v3 != NULL || (!v28 && v25 == 1)) { do { if (v3 == NULL) { v3 = ai_search_environ(critter, ITEM_TYPE_DRUG); } if (v3 != NULL) { v3 = ai_retrieve_object(critter, v3); } else { Object* v22 = ai_search_environ(critter, ITEM_TYPE_MISC); if (v22 != NULL) { v3 = ai_retrieve_object(critter, v22); } } if (v3 != NULL && item_remove_mult(critter, v3, 1) == 0) { if (item_d_take_drug(critter, v3) == -1) { item_add_force(critter, v3, 1); } else { ai_magic_hands(critter, v3, 5000); obj_connect(v3, critter->tile, critter->elevation, NULL); obj_destroy(v3); v3 = NULL; } if (critter->data.critter.combat.ap < 2) { critter->data.critter.combat.ap = 0; } else { critter->data.critter.combat.ap -= 2; } } } while (v3 != NULL && critter->data.critter.combat.ap >= 2); } return 0; } // 0x428868 static void ai_run_away(Object* a1, Object* a2) { if (a2 == NULL) { a2 = obj_dude; } CritterCombatData* combatData = &(a1->data.critter.combat); AiPacket* ai = ai_cap(a1); int distance = obj_dist(a1, a2); if (distance < ai->max_dist) { combatData->maneuver |= CRITTER_MANUEVER_FLEEING; int rotation = tile_dir(a2->tile, a1->tile); int destination; int actionPoints = combatData->ap; for (; actionPoints > 0; actionPoints -= 1) { destination = tile_num_in_direction(a1->tile, rotation, actionPoints); if (make_path(a1, a1->tile, destination, NULL, 1) > 0) { break; } destination = tile_num_in_direction(a1->tile, (rotation + 1) % ROTATION_COUNT, actionPoints); if (make_path(a1, a1->tile, destination, NULL, 1) > 0) { break; } destination = tile_num_in_direction(a1->tile, (rotation + 5) % ROTATION_COUNT, actionPoints); if (make_path(a1, a1->tile, destination, NULL, 1) > 0) { break; } } if (actionPoints > 0) { register_begin(ANIMATION_REQUEST_RESERVED); combatai_msg(a1, NULL, AI_MESSAGE_TYPE_RUN, 0); register_object_run_to_tile(a1, destination, a1->elevation, combatData->ap, 0); if (register_end() == 0) { combat_turn_run(); } } } else { combatData->maneuver |= CRITTER_MANEUVER_STOP_ATTACKING; } } // 0x42899C static int ai_move_away(Object* a1, Object* a2, int a3) { if (ai_cap(a1)->distance == DISTANCE_STAY) { return -1; } if (obj_dist(a1, a2) <= a3) { int actionPoints = a1->data.critter.combat.ap; if (a3 < actionPoints) { actionPoints = a3; } int rotation = tile_dir(a2->tile, a1->tile); int destination; int actionPointsLeft = actionPoints; for (; actionPointsLeft > 0; actionPointsLeft -= 1) { destination = tile_num_in_direction(a1->tile, rotation, actionPointsLeft); if (make_path(a1, a1->tile, destination, NULL, 1) > 0) { break; } destination = tile_num_in_direction(a1->tile, (rotation + 1) % ROTATION_COUNT, actionPointsLeft); if (make_path(a1, a1->tile, destination, NULL, 1) > 0) { break; } destination = tile_num_in_direction(a1->tile, (rotation + 5) % ROTATION_COUNT, actionPointsLeft); if (make_path(a1, a1->tile, destination, NULL, 1) > 0) { break; } } if (actionPoints > 0) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_tile(a1, destination, a1->elevation, actionPoints, 0); if (register_end() == 0) { combat_turn_run(); } } } return 0; } // 0x428AC4 static bool ai_find_friend(Object* a1, int a2, int a3) { Object* v1 = ai_find_nearest_team(a1, a1, 1); if (v1 == NULL) { return false; } int distance = obj_dist(a1, v1); if (distance > a2) { return false; } if (a3 > distance) { int v2 = obj_dist(a1, v1) - a3; ai_move_steps_closer(a1, v1, v2, 0); } return true; } // Compare objects by distance to origin. // // 0x428B1C static int compare_nearer(const void* a1, const void* a2) { Object* v1 = *(Object**)a1; Object* v2 = *(Object**)a2; if (v1 == NULL) { if (v2 == NULL) { return 0; } return 1; } else { if (v2 == NULL) { return -1; } } int distance1 = obj_dist(v1, combat_obj); int distance2 = obj_dist(v2, combat_obj); if (distance1 < distance2) { return -1; } else if (distance1 > distance2) { return 1; } else { return 0; } } // NOTE: Inlined. // // 0x428B74 static void ai_sort_list_distance(Object** critterList, int length, Object* origin) { combat_obj = origin; qsort(critterList, length, sizeof(*critterList), compare_nearer); } // qsort compare function - melee then ranged. // // 0x428B8C int compare_strength(const void* p1, const void* p2) { Object* a1 = *(Object**)p1; Object* a2 = *(Object**)p2; if (a1 == NULL) { if (a2 == NULL) { return 0; } return 1; } if (a2 == NULL) { return -1; } int v3 = combatai_rating(a1); int v5 = combatai_rating(a2); if (v3 < v5) { return -1; } if (v3 > v5) { return 1; } return 0; } // NOTE: Inlined. // // 0x428BD0 static void ai_sort_list_strength(Object** critterList, int length) { qsort(critterList, length, sizeof(*critterList), compare_strength); } // qsort compare unction - ranged then melee // // 0x428BE4 int compare_weakness(const void* p1, const void* p2) { Object* a1 = *(Object**)p1; Object* a2 = *(Object**)p2; if (a1 == NULL) { if (a2 == NULL) { return 0; } return 1; } if (a2 == NULL) { return -1; } int v3 = combatai_rating(a1); int v5 = combatai_rating(a2); if (v3 < v5) { return 1; } if (v3 > v5) { return -1; } return 0; } // NOTE: Inlined. // // 0x428C28 static void ai_sort_list_weakness(Object** critterList, int length) { qsort(critterList, length, sizeof(*critterList), compare_weakness); } // 0x428C3C static Object* ai_find_nearest_team(Object* a1, Object* a2, int a3) { int i; Object* obj; if (a2 == NULL) { return NULL; } if (curr_crit_num == 0) { return NULL; } // NOTE: Uninline. ai_sort_list_distance(curr_crit_list, curr_crit_num, a1); for (i = 0; i < curr_crit_num; i++) { obj = curr_crit_list[i]; if (a1 != obj && !(obj->data.critter.combat.results & 0x80) && (((a3 & 0x02) && a2->data.critter.combat.team != obj->data.critter.combat.team) || ((a3 & 0x01) && a2->data.critter.combat.team == obj->data.critter.combat.team))) { return obj; } } return NULL; } // 0x428CF4 static Object* ai_find_nearest_team_in_combat(Object* a1, Object* a2, int a3) { if (a2 == NULL) { return NULL; } if (curr_crit_num == 0) { return NULL; } int team = a2->data.critter.combat.team; // NOTE: Uninline. ai_sort_list_distance(curr_crit_list, curr_crit_num, a1); for (int index = 0; index < curr_crit_num; index++) { Object* obj = curr_crit_list[index]; if (obj != a1 && (obj->data.critter.combat.results & DAM_DEAD) == 0 && (((a3 & 0x02) != 0 && team != obj->data.critter.combat.team) || ((a3 & 0x01) != 0 && team == obj->data.critter.combat.team))) { if (obj->data.critter.combat.whoHitMe != NULL) { return obj; } } } return NULL; } // 0x428DB0 static int ai_find_attackers(Object* a1, Object** a2, Object** a3, Object** a4) { if (a2 != NULL) { *a2 = NULL; } if (a3 != NULL) { *a3 = NULL; } if (*a4 != NULL) { *a4 = NULL; } if (curr_crit_num == 0) { return 0; } // NOTE: Uninline. ai_sort_list_distance(curr_crit_list, curr_crit_num, a1); int foundTargetCount = 0; int team = a1->data.critter.combat.team; for (int index = 0; foundTargetCount < 3 && index < curr_crit_num; index++) { Object* candidate = curr_crit_list[index]; if (candidate != a1) { if (a2 != NULL && *a2 == NULL) { if ((candidate->data.critter.combat.results & DAM_DEAD) == 0 && candidate->data.critter.combat.whoHitMe == a1) { foundTargetCount++; *a2 = candidate; } } if (a3 != NULL && *a3 == NULL) { if (team == candidate->data.critter.combat.team) { Object* whoHitCandidate = candidate->data.critter.combat.whoHitMe; if (whoHitCandidate != NULL && whoHitCandidate != a1 && team != whoHitCandidate->data.critter.combat.team && (whoHitCandidate->data.critter.combat.results & DAM_DEAD) == 0) { foundTargetCount++; *a3 = whoHitCandidate; } } } if (a4 != NULL && *a4 == NULL) { if (candidate->data.critter.combat.team != team && (candidate->data.critter.combat.results & DAM_DEAD) == 0) { Object* whoHitCandidate = candidate->data.critter.combat.whoHitMe; if (whoHitCandidate != NULL && whoHitCandidate->data.critter.combat.team == team) { foundTargetCount++; *a4 = candidate; } } } } } return 0; } // ai_danger_source // 0x428F4C Object* ai_danger_source(Object* a1) { if (a1 == NULL) { return NULL; } bool v2 = false; int attackWho; Object* targets[4]; targets[0] = NULL; if (isPartyMember(a1)) { int disposition = a1 != NULL ? ai_cap(a1)->disposition : 0; switch (disposition + 1) { case 1: case 2: case 3: case 4: v2 = true; break; case 0: case 5: v2 = false; break; } if (v2 && ai_cap(a1)->distance == 1) { v2 = false; } attackWho = ai_cap(a1)->attack_who; switch (attackWho) { case ATTACK_WHO_WHOMEVER_ATTACKING_ME: { Object* candidate = combatAIInfoGetLastTarget(obj_dude); if (candidate == NULL || a1->data.critter.combat.team == candidate->data.critter.combat.team) { break; } if (make_path_func(a1, a1->tile, obj_dude->data.critter.combat.whoHitMe->tile, NULL, 0, obj_blocking_at) == 0 && combat_check_bad_shot(a1, candidate, HIT_MODE_RIGHT_WEAPON_PRIMARY, false) != COMBAT_BAD_SHOT_OK) { debug_printf("\nai_danger_source: %s couldn't attack at target! Picking alternate!", critter_name(a1)); break; } if (v2 && critter_is_fleeing(a1)) { break; } return candidate; } case ATTACK_WHO_STRONGEST: case ATTACK_WHO_WEAKEST: case ATTACK_WHO_CLOSEST: a1->data.critter.combat.whoHitMe = NULL; break; default: break; } } else { attackWho = -1; } Object* whoHitMe = a1->data.critter.combat.whoHitMe; if (whoHitMe == NULL || a1 == whoHitMe) { targets[0] = NULL; } else { if ((whoHitMe->data.critter.combat.results & DAM_DEAD) == 0) { if (attackWho == ATTACK_WHO_WHOMEVER || attackWho == -1) { return whoHitMe; } } else { if (whoHitMe->data.critter.combat.team != a1->data.critter.combat.team) { targets[0] = ai_find_nearest_team(a1, whoHitMe, 1); } else { targets[0] = NULL; } } } ai_find_attackers(a1, &(targets[1]), &(targets[2]), &(targets[3])); if (v2) { for (int index = 0; index < 4; index++) { if (targets[index] != NULL && critter_is_fleeing(targets[index])) { targets[index] = NULL; } } } switch (attackWho) { case ATTACK_WHO_STRONGEST: // NOTE: Uninline. ai_sort_list_strength(targets, 4); break; case ATTACK_WHO_WEAKEST: // NOTE: Uninline. ai_sort_list_weakness(targets, 4); break; default: // NOTE: Uninline. ai_sort_list_distance(targets, 4, a1); break; } for (int index = 0; index < 4; index++) { Object* candidate = targets[index]; if (candidate != NULL && is_within_perception(a1, candidate)) { if (make_path_func(a1, a1->tile, candidate->tile, NULL, 0, obj_blocking_at) != 0 || combat_check_bad_shot(a1, candidate, HIT_MODE_RIGHT_WEAPON_PRIMARY, false) == COMBAT_BAD_SHOT_OK) { return candidate; } debug_printf("\nai_danger_source: I couldn't get at my target! Picking alternate!"); } } return NULL; } // 0x4291C4 int caiSetupTeamCombat(Object* a1, Object* a2) { Object* obj; obj = obj_find_first_at(a1->elevation); while (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER && obj != obj_dude) { obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01; } obj = obj_find_next_at(); } attackerTeamObj = a1; targetTeamObj = a2; return 0; } // 0x_429210 int caiTeamCombatInit(Object** a1, int a2) { int v9; int v10; int i; Object* v8; if (a1 == NULL) { return -1; } if (a2 == 0) { return 0; } if (attackerTeamObj == NULL) { return 0; } v9 = attackerTeamObj->data.critter.combat.team; v10 = targetTeamObj->data.critter.combat.team; for (i = 0; i < a2; i++) { if (a1[i]->data.critter.combat.team == v9) { v8 = targetTeamObj; } else if (a1[i]->data.critter.combat.team == v10) { v8 = attackerTeamObj; } else { continue; } a1[i]->data.critter.combat.whoHitMe = ai_find_nearest_team(a1[i], v8, 1); } attackerTeamObj = NULL; targetTeamObj = NULL; return 0; } // 0x4292C0 void caiTeamCombatExit() { targetTeamObj = 0; attackerTeamObj = 0; } // 0x4292D4 static int ai_have_ammo(Object* critter_obj, Object* weapon_obj, Object** out_ammo_obj) { int v9; Object* ammo_obj; if (out_ammo_obj) { *out_ammo_obj = NULL; } if (weapon_obj->pid == PROTO_ID_SOLAR_SCORCHER) { return light_get_ambient() > 62259; } v9 = -1; while (1) { ammo_obj = inven_find_type(critter_obj, 4, &v9); if (ammo_obj == NULL) { break; } if (item_w_can_reload(weapon_obj, ammo_obj)) { if (out_ammo_obj) { *out_ammo_obj = ammo_obj; } return 1; } if (item_w_anim_code(weapon_obj)) { if (item_w_range(critter_obj, 2) < 3) { inven_unwield(critter_obj, 1); } } else { inven_unwield(critter_obj, 1); } } return 0; } // 0x42938C static bool caiHasWeapPrefType(AiPacket* ai, int attackType) { int bestWeapon = ai->best_weapon + 1; for (int index = 0; index < 5; index++) { if (attackType == weapPrefOrderings[bestWeapon][index]) { return true; } } return false; } // 0x4293BC static Object* ai_best_weapon(Object* attacker, Object* weapon1, Object* weapon2, Object* defender) { if (attacker == NULL) { return NULL; } AiPacket* ai = ai_cap(attacker); if (ai->best_weapon == BEST_WEAPON_RANDOM) { return roll_random(1, 100) <= 50 ? weapon1 : weapon2; } int minDamage; int maxDamage; int v24 = 0; int v25 = 999; int v26 = 999; int avgDamage1 = 0; Attack attack; combat_ctd_init(&attack, attacker, defender, HIT_MODE_RIGHT_WEAPON_PRIMARY, HIT_LOCATION_TORSO); int attackType1; int distance; int attackType2; int avgDamage2 = 0; int v23 = 0; // NOTE: weaponClass1 and weaponClass2 both use ESI but they are not // initialized. I'm not sure if this is right, but at least it doesn't // crash. attackType1 = -1; attackType2 = -1; if (weapon1 != NULL) { attackType1 = item_w_subtype(weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY); if (item_w_damage_min_max(weapon1, &minDamage, &maxDamage) == -1) { return NULL; } avgDamage1 = (maxDamage - minDamage) / 2; if (item_w_area_damage_radius(weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY) > 0 && defender != NULL) { attack.weapon = weapon1; compute_explosion_on_extras(&attack, 0, item_w_is_grenade(weapon1), 1); avgDamage1 *= attack.extrasLength + 1; } // TODO: Probably an error, why it takes [weapon2], should likely use // [weapon1]. if (item_w_perk(weapon2) != -1) { avgDamage1 *= 5; } if (defender != NULL) { if (combat_safety_invalidate_weapon(attacker, weapon1, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL)) { v24 = 1; } } if (item_is_hidden(weapon1)) { return weapon1; } } else { distance = obj_dist(attacker, defender); if (item_w_range(attacker, HIT_MODE_PUNCH) >= distance) { attackType1 = ATTACK_TYPE_UNARMED; } } if (!v24) { for (int index = 0; index < ATTACK_TYPE_COUNT; index++) { if (weapPrefOrderings[ai->best_weapon + 1][index] == attackType1) { v26 = index; break; } } } if (weapon2 != NULL) { attackType2 = item_w_subtype(weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY); if (item_w_damage_min_max(weapon2, &minDamage, &maxDamage) == -1) { return NULL; } avgDamage2 = (maxDamage - minDamage) / 2; if (item_w_area_damage_radius(weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY) > 0 && defender != NULL) { attack.weapon = weapon2; compute_explosion_on_extras(&attack, 0, item_w_is_grenade(weapon2), 1); avgDamage2 *= attack.extrasLength + 1; } if (item_w_perk(weapon2) != -1) { avgDamage2 *= 5; } if (defender != NULL) { if (combat_safety_invalidate_weapon(attacker, weapon2, HIT_MODE_RIGHT_WEAPON_PRIMARY, defender, NULL)) { v23 = 1; } } if (item_is_hidden(weapon2)) { return weapon2; } } else { if (distance == 0) { distance = obj_dist(attacker, weapon1); } if (item_w_range(attacker, HIT_MODE_PUNCH) >= distance) { attackType2 = ATTACK_TYPE_UNARMED; } } if (!v23) { for (int index = 0; index < ATTACK_TYPE_COUNT; index++) { if (weapPrefOrderings[ai->best_weapon + 1][index] == attackType2) { v25 = index; break; } } } if (v26 == v25) { if (v26 == 999) { return NULL; } if (abs(avgDamage2 - avgDamage1) <= 5) { return item_cost(weapon2) > item_cost(weapon1) ? weapon2 : weapon1; } return avgDamage2 > avgDamage1 ? weapon2 : weapon1; } if (weapon1 != NULL && weapon1->pid == PROTO_ID_FLARE && weapon2 != NULL) { return weapon2; } if (weapon2 != NULL && weapon2->pid == PROTO_ID_FLARE && weapon1 != NULL) { return weapon1; } if ((ai->best_weapon == -1 || ai->best_weapon >= BEST_WEAPON_UNARMED_OVER_THROW) && abs(avgDamage2 - avgDamage1) > 5) { return avgDamage2 > avgDamage1 ? weapon2 : weapon1; } return v26 > v25 ? weapon2 : weapon1; } // 0x4298EC static bool ai_can_use_weapon(Object* critter, Object* weapon, int hitMode) { int damageFlags = critter->data.critter.combat.results; if ((damageFlags & DAM_CRIP_ARM_LEFT) != 0 && (damageFlags & DAM_CRIP_ARM_RIGHT) != 0) { return false; } if ((damageFlags & DAM_CRIP_ARM_ANY) != 0 && item_w_is_2handed(weapon)) { return false; } int rotation = critter->rotation + 1; int animationCode = item_w_anim_code(weapon); int v9 = item_w_anim_weap(weapon, hitMode); int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, v9, animationCode, rotation); if (!art_exists(fid)) { return false; } int skill = item_w_skill(weapon, hitMode); AiPacket* ai = ai_cap(critter); if (skill_level(critter, skill) < ai->min_to_hit) { return false; } int attackType = item_w_subtype(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY); return caiHasWeapPrefType(ai, attackType) != 0; } // 0x4299A0 Object* ai_search_inven_weap(Object* critter, int a2, Object* a3) { int bodyType = critter_body_type(critter); if (bodyType != BODY_TYPE_BIPED && bodyType != BODY_TYPE_ROBOTIC && critter->pid != PROTO_ID_0x1000098) { return NULL; } int token = -1; Object* bestWeapon = NULL; Object* rightHandWeapon = inven_right_hand(critter); while (true) { Object* weapon = inven_find_type(critter, ITEM_TYPE_WEAPON, &token); if (weapon == NULL) { break; } if (weapon == rightHandWeapon) { continue; } if (a2) { if (item_w_primary_mp_cost(weapon) > critter->data.critter.combat.ap) { continue; } } if (!ai_can_use_weapon(critter, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY)) { continue; } if (item_w_subtype(weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY) == ATTACK_TYPE_RANGED) { if (item_w_curr_ammo(weapon) == 0) { if (!ai_have_ammo(critter, weapon, NULL)) { continue; } } } bestWeapon = ai_best_weapon(critter, bestWeapon, weapon, a3); } return bestWeapon; } // Finds new best armor (other than what's already equipped) based on the armor score. // // 0x429A6C Object* ai_search_inven_armor(Object* critter) { if (!isPartyMember(critter)) { return NULL; } // Calculate armor score - it's a unitless combination of armor class and bonuses across // all damage types. int armorScore = 0; Object* armor = inven_worn(critter); if (armor != NULL) { armorScore = item_ar_ac(armor); for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType++) { armorScore += item_ar_dr(armor, damageType); armorScore += item_ar_dt(armor, damageType); } } else { armorScore = 0; } Object* bestArmor = NULL; int v15 = -1; while (true) { Object* candidate = inven_find_type(critter, ITEM_TYPE_ARMOR, &v15); if (candidate == NULL) { break; } if (armor != candidate) { int candidateScore = item_ar_ac(candidate); for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType++) { candidateScore += item_ar_dr(candidate, damageType); candidateScore += item_ar_dt(candidate, damageType); } if (candidateScore > armorScore) { armorScore = candidateScore; bestArmor = candidate; } } } return bestArmor; } // Returns true if critter can use given item. // // That means the item is one of it's primary desires, // or it's a humanoid being with intelligence at least 3, // and the iteam is a something healing. // // 0x429B44 static bool ai_can_use_drug(Object* critter, Object* item) { if (critter == NULL) { return false; } if (item == NULL) { return false; } AiPacket* ai = ai_cap_from_packet(critter->data.critter.combat.aiPacket); if (ai == NULL) { return false; } for (int index = 0; index < AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT; index++) { if (item->pid == ai->chem_primary_desire[index]) { return true; } } if (critter_body_type(critter) != BODY_TYPE_BIPED) { return false; } int killType = critterGetKillType(critter); if (killType != KILL_TYPE_MAN && killType != KILL_TYPE_WOMAN && killType != KILL_TYPE_SUPER_MUTANT && killType != KILL_TYPE_GHOUL && killType != KILL_TYPE_CHILD) { return false; } if (critterGetStat(critter, STAT_INTELLIGENCE) < 3) { return false; } int itemPid = item->pid; if (itemPid != PROTO_ID_STIMPACK && itemPid != PROTO_ID_SUPER_STIMPACK && itemPid != PROTO_ID_HEALING_POWDER) { return false; } return true; } // Find best item type to use? // // 0x429C18 static Object* ai_search_environ(Object* critter, int itemType) { if (critter_body_type(critter) != BODY_TYPE_BIPED) { return NULL; } Object** objects; int count = obj_create_list(-1, map_elevation, OBJ_TYPE_ITEM, &objects); if (count == 0) { return NULL; } // NOTE: Uninline. ai_sort_list_distance(objects, count, critter); int perception = critterGetStat(critter, STAT_PERCEPTION) + 5; Object* item2 = inven_right_hand(critter); Object* foundItem = NULL; for (int index = 0; index < count; index++) { Object* item = objects[index]; int distance = obj_dist(critter, item); if (distance > perception) { break; } if (item_get_type(item) == itemType) { switch (itemType) { case ITEM_TYPE_WEAPON: if (ai_can_use_weapon(critter, item, HIT_MODE_RIGHT_WEAPON_PRIMARY)) { foundItem = item; } break; case ITEM_TYPE_AMMO: if (item_w_can_reload(item2, item)) { foundItem = item; } break; case ITEM_TYPE_DRUG: case ITEM_TYPE_MISC: if (ai_can_use_drug(critter, item)) { foundItem = item; } break; } if (foundItem != NULL) { break; } } } obj_delete_list(objects); return foundItem; } // 0x429D60 static Object* ai_retrieve_object(Object* a1, Object* a2) { if (action_get_an_object(a1, a2) != 0) { return NULL; } combat_turn_run(); Object* v3 = inven_find_id(a1, a2->id); // TODO: Not sure about this one. if (v3 != NULL || a2->owner != NULL) { a2 = NULL; } combatAIInfoSetLastItem(v3, a2); return v3; } // 0x429DB4 static int ai_pick_hit_mode(Object* a1, Object* a2, Object* a3) { if (a2 == NULL) { return HIT_MODE_PUNCH; } if (item_get_type(a2) != ITEM_TYPE_WEAPON) { return HIT_MODE_PUNCH; } int attackType = item_w_subtype(a2, HIT_MODE_RIGHT_WEAPON_SECONDARY); int intelligence = critterGetStat(a1, STAT_INTELLIGENCE); if (attackType == ATTACK_TYPE_NONE || !ai_can_use_weapon(a1, a2, HIT_MODE_RIGHT_WEAPON_SECONDARY)) { return HIT_MODE_RIGHT_WEAPON_PRIMARY; } bool useSecondaryMode = false; AiPacket* ai = ai_cap(a1); if (ai == NULL) { return HIT_MODE_PUNCH; } if (ai->area_attack_mode != -1) { switch (ai->area_attack_mode) { case AREA_ATTACK_MODE_ALWAYS: useSecondaryMode = true; break; case AREA_ATTACK_MODE_SOMETIMES: if (roll_random(1, ai->secondary_freq) == 1) { useSecondaryMode = true; } break; case AREA_ATTACK_MODE_BE_SURE: if (determine_to_hit(a1, a3, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 85 && !combat_safety_invalidate_weapon(a1, a2, 3, a3, 0)) { useSecondaryMode = true; } break; case AREA_ATTACK_MODE_BE_CAREFUL: if (determine_to_hit(a1, a3, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 50 && !combat_safety_invalidate_weapon(a1, a2, 3, a3, 0)) { useSecondaryMode = true; } break; case AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE: if (determine_to_hit(a1, a3, HIT_LOCATION_TORSO, HIT_MODE_RIGHT_WEAPON_SECONDARY) >= 95 && !combat_safety_invalidate_weapon(a1, a2, 3, a3, 0)) { useSecondaryMode = true; } break; } } else { if (intelligence < 6 || obj_dist(a1, a3) < 10) { if (roll_random(1, ai->secondary_freq) == 1) { useSecondaryMode = true; } } } if (useSecondaryMode) { if (!caiHasWeapPrefType(ai, attackType)) { useSecondaryMode = false; } } if (useSecondaryMode) { if (attackType != ATTACK_TYPE_THROW || ai_search_inven_weap(a1, 0, a3) != NULL || stat_result(a1, STAT_INTELLIGENCE, 0, NULL) <= 1) { return HIT_MODE_RIGHT_WEAPON_SECONDARY; } } return HIT_MODE_RIGHT_WEAPON_PRIMARY; } // 0x429FC8 static int ai_move_steps_closer(Object* a1, Object* a2, int actionPoints, int a4) { if (actionPoints <= 0) { return -1; } int distance = ai_cap(a1)->distance; if (distance == DISTANCE_STAY) { return -1; } if (distance == DISTANCE_STAY_CLOSE) { if (a2 != obj_dude) { int v10 = obj_dist(a1, obj_dude); if (v10 > 5 && obj_dist(a2, obj_dude) > 5 && v10 + actionPoints > 5) { return -1; } } } if (obj_dist(a1, a2) <= 1) { return -1; } register_begin(ANIMATION_REQUEST_RESERVED); if (a4) { combatai_msg(a1, NULL, AI_MESSAGE_TYPE_MOVE, 0); } Object* v18 = a2; bool shouldUnhide; if ((a2->flags & 0x800) != 0) { shouldUnhide = true; a2->flags |= OBJECT_HIDDEN; } else { shouldUnhide = false; } if (make_path_func(a1, a1->tile, a2->tile, NULL, 0, obj_blocking_at) == 0) { moveBlockObj = NULL; if (make_path_func(a1, a1->tile, a2->tile, NULL, 0, obj_ai_blocking_at) == 0 && moveBlockObj != NULL && PID_TYPE(moveBlockObj->pid) == OBJ_TYPE_CRITTER) { if (shouldUnhide) { a2->flags &= ~OBJECT_HIDDEN; } a2 = moveBlockObj; if ((a2->flags & 0x800) != 0) { shouldUnhide = true; a2->flags |= OBJECT_HIDDEN; } else { shouldUnhide = false; } } } if (shouldUnhide) { a2->flags &= ~OBJECT_HIDDEN; } int tile = a2->tile; if (a2 == v18) { cai_retargetTileFromFriendlyFire(a1, a2, &tile); } if (actionPoints >= critterGetStat(a1, STAT_MAXIMUM_ACTION_POINTS) / 2 && artCritterFidShouldRun(a1->fid)) { if ((a2->flags & OBJECT_MULTIHEX) != 0) { register_object_run_to_object(a1, a2, actionPoints, 0); } else { register_object_run_to_tile(a1, tile, a1->elevation, actionPoints, 0); } } else { if ((a2->flags & OBJECT_MULTIHEX) != 0) { register_object_move_to_object(a1, a2, actionPoints, 0); } else { register_object_move_to_tile(a1, tile, a1->elevation, actionPoints, 0); } } if (register_end() != 0) { return -1; } combat_turn_run(); return 0; } // NOTE: Inlined. // // 0x42A1C0 static int ai_move_closer(Object* a1, Object* a2, int a3) { return ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, a3); } // 0x42A1D4 static int cai_retargetTileFromFriendlyFire(Object* source, Object* target, int* tilePtr) { if (source == NULL) { return -1; } if (target == NULL) { return -1; } if (tilePtr == NULL) { return -1; } if (*tilePtr == -1) { return -1; } if (curr_crit_num == 0) { return -1; } int tiles[32]; AiRetargetData aiRetargetData; aiRetargetData.source = source; aiRetargetData.target = target; aiRetargetData.sourceTeam = source->data.critter.combat.team; aiRetargetData.sourceRating = combatai_rating(source); aiRetargetData.critterCount = 0; aiRetargetData.tiles = tiles; aiRetargetData.notSameTile = *tilePtr != source->tile; aiRetargetData.currentTileIndex = 0; aiRetargetData.sourceIntelligence = critterGetStat(source, STAT_INTELLIGENCE); for (int index = 0; index < 32; index++) { tiles[index] = -1; } for (int index = 0; index < curr_crit_num; index++) { Object* obj = curr_crit_list[index]; if ((obj->data.critter.combat.results & DAM_DEAD) == 0 && obj->data.critter.combat.team == aiRetargetData.sourceTeam && combatAIInfoGetLastTarget(obj) == aiRetargetData.target && obj != aiRetargetData.source) { int rating = combatai_rating(obj); if (rating >= aiRetargetData.sourceRating) { aiRetargetData.critterList[aiRetargetData.critterCount] = obj; aiRetargetData.ratingList[aiRetargetData.critterCount] = rating; aiRetargetData.critterCount += 1; } } } // NOTE: Uninline. ai_sort_list_distance(aiRetargetData.critterList, aiRetargetData.critterCount, source); if (cai_retargetTileFromFriendlyFireSubFunc(&aiRetargetData, *tilePtr) == 0) { int minDistance = 99999; int minDistanceIndex = -1; for (int index = 0; index < 32; index++) { int tile = tiles[index]; if (tile == -1) { break; } if (obj_blocking_at(NULL, tile, source->elevation) == 0) { int distance = tile_dist(*tilePtr, tile); if (distance < minDistance) { minDistance = distance; minDistanceIndex = index; } } } if (minDistanceIndex != -1) { *tilePtr = tiles[minDistanceIndex]; } } return 0; } // 0x42A410 static int cai_retargetTileFromFriendlyFireSubFunc(AiRetargetData* aiRetargetData, int tile) { if (aiRetargetData->sourceIntelligence <= 0) { return 0; } int distance = 1; for (int index = 0; index < aiRetargetData->critterCount; index++) { Object* critter = aiRetargetData->critterList[index]; if (cai_attackWouldIntersect(critter, aiRetargetData->target, aiRetargetData->source, tile, &distance)) { debug_printf("In the way!"); aiRetargetData->tiles[aiRetargetData->currentTileIndex] = tile_num_in_direction(tile, (critter->rotation + 1) % ROTATION_COUNT, distance); aiRetargetData->tiles[aiRetargetData->currentTileIndex + 1] = tile_num_in_direction(tile, (critter->rotation + 5) % ROTATION_COUNT, distance); aiRetargetData->sourceIntelligence -= 2; aiRetargetData->currentTileIndex += 2; break; } } return 0; } // 0x42A518 static bool cai_attackWouldIntersect(Object* attacker, Object* defender, Object* attackerFriend, int tile, int* distance) { int hitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY; bool aiming = false; if (attacker == obj_dude) { intface_get_attack(&hitMode, &aiming); } Object* weapon = item_hit_with(attacker, hitMode); if (weapon == NULL) { return false; } if (item_w_range(attacker, hitMode) < 1) { return false; } Object* object = NULL; make_straight_path_func(attacker, attacker->tile, defender->tile, NULL, &object, 32, obj_shoot_blocking_at); if (object != attackerFriend) { if (!combatTestIncidentalHit(attacker, defender, attackerFriend, weapon)) { return false; } } return true; } // 0x42A5B8 static int ai_switch_weapons(Object* a1, int* hitMode, Object** weapon, Object* a4) { *weapon = NULL; *hitMode = HIT_MODE_PUNCH; Object* bestWeapon = ai_search_inven_weap(a1, 1, a4); if (bestWeapon != NULL) { *weapon = bestWeapon; *hitMode = ai_pick_hit_mode(a1, bestWeapon, a4); } else { Object* v8 = ai_search_environ(a1, ITEM_TYPE_WEAPON); if (v8 == NULL) { if (item_w_mp_cost(a1, *hitMode, 0) <= a1->data.critter.combat.ap) { return 0; } return -1; } Object* v9 = ai_retrieve_object(a1, v8); if (v9 != NULL) { *weapon = v9; *hitMode = ai_pick_hit_mode(a1, v9, a4); } } if (*weapon != NULL) { inven_wield(a1, *weapon, 1); combat_turn_run(); if (item_w_mp_cost(a1, *hitMode, 0) <= a1->data.critter.combat.ap) { return 0; } } return -1; } // 0x42A670 static int ai_called_shot(Object* a1, Object* a2, int a3) { AiPacket* ai; int v5; int v6; int v7; int combat_difficulty; v5 = 3; if (item_w_mp_cost(a1, a3, 1) <= a1->data.critter.combat.ap) { if (item_w_called_shot(a1, a3)) { ai = ai_cap(a1); if (roll_random(1, ai->called_freq) == 1) { combat_difficulty = 1; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combat_difficulty); if (combat_difficulty) { if (combat_difficulty == 2) { v6 = 3; } else { v6 = 5; } } else { v6 = 7; } if (critterGetStat(a1, STAT_INTELLIGENCE) >= v6) { v5 = roll_random(0, 8); v7 = determine_to_hit(a1, a2, a3, v5); if (v7 < ai->min_to_hit) { v5 = 3; } } } } } return v5; } // 0x42A748 static int ai_attack(Object* a1, Object* a2, int a3) { int v6; if (a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) { return -1; } register_begin(ANIMATION_REQUEST_RESERVED); register_object_turn_towards(a1, a2->tile); register_end(); combat_turn_run(); v6 = ai_called_shot(a1, a2, a3); if (combat_attack(a1, a2, a3, v6)) { return -1; } combat_turn_run(); return 0; } // 0x42A7D8 static int ai_try_attack(Object* a1, Object* a2) { critter_set_who_hit_me(a1, a2); CritterCombatData* combatData = &(a1->data.critter.combat); int v38 = 1; Object* weapon = inven_right_hand(a1); if (weapon != NULL && item_get_type(weapon) != ITEM_TYPE_WEAPON) { weapon = NULL; } int hitMode = ai_pick_hit_mode(a1, weapon, a2); int minToHit = ai_cap(a1)->min_to_hit; int actionPoints = a1->data.critter.combat.ap; int v31 = 0; int v42 = 0; if (weapon != NULL || (critter_body_type(a2) == BODY_TYPE_BIPED && ((a2->fid & 0xF000) >> 12 == 0) && art_exists(art_id(OBJ_TYPE_CRITTER, a1->fid & 0xFFF, ANIM_THROW_PUNCH, 0, a1->rotation + 1)))) { if (combat_safety_invalidate_weapon(a1, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, a2, &v31)) { ai_switch_weapons(a1, &hitMode, &weapon, a2); } } else { ai_switch_weapons(a1, &hitMode, &weapon, a2); } unsigned char v30[800]; Object* ammo = NULL; for (int attempt = 0; attempt < 10; attempt++) { if ((combatData->results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) { break; } int reason = combat_check_bad_shot(a1, a2, hitMode, false); if (reason == COMBAT_BAD_SHOT_NO_AMMO) { // out of ammo if (ai_have_ammo(a1, weapon, &ammo)) { int v9 = item_w_reload(weapon, ammo); if (v9 == 0 && ammo != NULL) { obj_destroy(ammo); } if (v9 != -1) { int volume = gsound_compute_relative_volume(a1); const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL); gsound_play_sfx_file_volume(sfx, volume); ai_magic_hands(a1, weapon, 5002); int actionPoints = a1->data.critter.combat.ap; if (actionPoints >= 2) { a1->data.critter.combat.ap = actionPoints - 2; } else { a1->data.critter.combat.ap = 0; } } } else { ammo = ai_search_environ(a1, ITEM_TYPE_AMMO); if (ammo != NULL) { ammo = ai_retrieve_object(a1, ammo); if (ammo != NULL) { int v15 = item_w_reload(weapon, ammo); if (v15 == 0) { obj_destroy(ammo); } if (v15 != -1) { int volume = gsound_compute_relative_volume(a1); const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon, hitMode, NULL); gsound_play_sfx_file_volume(sfx, volume); ai_magic_hands(a1, weapon, 5002); int actionPoints = a1->data.critter.combat.ap; if (actionPoints >= 2) { a1->data.critter.combat.ap = actionPoints - 2; } else { a1->data.critter.combat.ap = 0; } } } } else { int volume = gsound_compute_relative_volume(a1); const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_OUT_OF_AMMO, weapon, hitMode, NULL); gsound_play_sfx_file_volume(sfx, volume); ai_magic_hands(a1, weapon, 5001); if (inven_unwield(a1, 1) == 0) { combat_turn_run(); } ai_switch_weapons(a1, &hitMode, &weapon, a2); } } } else if (reason == COMBAT_BAD_SHOT_NOT_ENOUGH_AP || reason == COMBAT_BAD_SHOT_ARM_CRIPPLED || reason == COMBAT_BAD_SHOT_BOTH_ARMS_CRIPPLED) { // 3 - not enough action points // 6 - crippled one arm for two-handed weapon // 7 - both hands crippled if (ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1) { return -1; } } else if (reason == COMBAT_BAD_SHOT_OUT_OF_RANGE) { // target out of range int accuracy = determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, v30); if (accuracy < minToHit) { const char* name = critter_name(a1); debug_printf("%s: FLEEING: Can't possibly Hit Target!", name); ai_run_away(a1, a2); return 0; } if (weapon != NULL) { if (ai_move_steps_closer(a1, a2, actionPoints, v38) == -1) { return -1; } v38 = 0; } else { if (ai_switch_weapons(a1, &hitMode, &weapon, a2) == -1 || weapon == NULL) { // NOTE: Uninline. if (ai_move_closer(a1, a2, v38) == -1) { return -1; } } v38 = 0; } } else if (reason == COMBAT_BAD_SHOT_AIM_BLOCKED) { // aim is blocked if (ai_move_steps_closer(a1, a2, a1->data.critter.combat.ap, v38) == -1) { return -1; } v38 = 0; } else if (reason == COMBAT_BAD_SHOT_OK) { int accuracy = determine_to_hit(a1, a2, HIT_LOCATION_UNCALLED, hitMode); if (v31) { if (ai_move_away(a1, a2, v31) == -1) { return -1; } } if (accuracy < minToHit) { int v22 = determine_to_hit_no_range(a1, a2, HIT_LOCATION_UNCALLED, hitMode, v30); if (v22 < minToHit) { const char* name = critter_name(a1); debug_printf("%s: FLEEING: Can't possibly Hit Target!", name); ai_run_away(a1, a2); return 0; } if (actionPoints > 0) { int v24 = make_path_func(a1, a1->tile, a2->tile, v30, 0, obj_blocking_at); if (v24 == 0) { v42 = actionPoints; } else { if (v24 < actionPoints) { actionPoints = v24; } int tile = a1->tile; int index; for (index = 0; index < actionPoints; index++) { tile = tile_num_in_direction(tile, v30[index], 1); v42++; int v27 = determine_to_hit_from_tile(a1, tile, a2, HIT_LOCATION_UNCALLED, hitMode); if (v27 >= minToHit) { break; } } if (index == actionPoints) { v42 = actionPoints; } } } if (ai_move_steps_closer(a1, a2, v42, v38) == -1) { const char* name = critter_name(a1); debug_printf("%s: FLEEING: Can't possibly get closer to Target!", name); ai_run_away(a1, a2); return 0; } v38 = 0; if (ai_attack(a1, a2, hitMode) == -1 || item_w_mp_cost(a1, hitMode, 0) > a1->data.critter.combat.ap) { return -1; } } else { if (ai_attack(a1, a2, hitMode) == -1 || item_w_mp_cost(a1, hitMode, 0) > a1->data.critter.combat.ap) { return -1; } } } } return -1; } // Something with using flare // // 0x42AE90 int cAIPrepWeaponItem(Object* critter, Object* item) { if (item != NULL && critterGetStat(critter, STAT_INTELLIGENCE) >= 3 && item->pid == PROTO_ID_FLARE && light_get_ambient() < 55705) { protinst_use_item(critter, item); } return 0; } // 0x42AECC void cai_attempt_w_reload(Object* critter_obj, int a2) { Object* weapon_obj; Object* ammo_obj; int v5; int v9; const char* sfx; int v10; weapon_obj = inven_right_hand(critter_obj); if (weapon_obj == NULL) { return; } v5 = item_w_curr_ammo(weapon_obj); if (v5 < item_w_max_ammo(weapon_obj) && ai_have_ammo(critter_obj, weapon_obj, &ammo_obj)) { v9 = item_w_reload(weapon_obj, ammo_obj); if (v9 == 0) { obj_destroy(ammo_obj); } if (v9 != -1 && isPartyMember(critter_obj)) { v10 = gsound_compute_relative_volume(critter_obj); sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon_obj, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL); gsound_play_sfx_file_volume(sfx, v10); if (a2) { ai_magic_hands(critter_obj, weapon_obj, 5002); } } } } // 0x42AF78 void combat_ai_begin(int a1, void* a2) { curr_crit_num = a1; if (a1 != 0) { curr_crit_list = (Object**)mem_malloc(sizeof(Object*) * a1); if (curr_crit_list) { memcpy(curr_crit_list, a2, sizeof(Object*) * a1); } else { curr_crit_num = 0; } } } // 0x42AFBC void combat_ai_over() { if (curr_crit_num) { mem_free(curr_crit_list); } curr_crit_num = 0; } // 0x42AFDC static int cai_perform_distance_prefs(Object* a1, Object* a2) { if (a1->data.critter.combat.ap <= 0) { return -1; } int distance = ai_cap(a1)->distance; if (a2 != NULL) { if ((a2->data.critter.combat.ap & DAM_DEAD) != 0) { a2 = NULL; } } switch (distance) { case DISTANCE_STAY_CLOSE: if (a1->data.critter.combat.whoHitMe != obj_dude) { int distance = obj_dist(a1, obj_dude); if (distance > 5) { ai_move_steps_closer(a1, obj_dude, distance - 5, 0); } } break; case DISTANCE_CHARGE: if (a2 != NULL) { // NOTE: Uninline. ai_move_closer(a1, a2, 1); } break; case DISTANCE_SNIPE: if (a2 != NULL) { if (obj_dist(a1, a2) < 10) { // NOTE: some odd code omitted ai_move_away(a1, a2, 10); } } break; } int tile = a1->tile; if (cai_retargetTileFromFriendlyFire(a1, a2, &tile) == 0 && tile != a1->tile) { register_begin(ANIMATION_REQUEST_RESERVED); register_object_move_to_tile(a1, tile, a1->elevation, a1->data.critter.combat.ap, 0); if (register_end() != 0) { return -1; } combat_turn_run(); } return 0; } // 0x42B100 static int cai_get_min_hp(AiPacket* ai) { if (ai == NULL) { return 0; } int run_away_mode = ai->run_away_mode; if (run_away_mode >= 0 && run_away_mode < RUN_AWAY_MODE_COUNT) { return runModeValues[run_away_mode]; } else if (run_away_mode == -1) { return ai->min_hp; } return 0; } // 0x42B130 void combat_ai(Object* a1, Object* a2) { // 0x51820C static int aiPartyMemberDistances[DISTANCE_COUNT] = { 5, 7, 7, 7, 50000, }; AiPacket* ai = ai_cap(a1); int hpRatio = cai_get_min_hp(ai); if (ai->run_away_mode != -1) { int v7 = critterGetStat(a1, STAT_MAXIMUM_HIT_POINTS) * hpRatio / 100; int minimumHitPoints = critterGetStat(a1, STAT_MAXIMUM_HIT_POINTS) - v7; int currentHitPoints = critterGetStat(a1, STAT_CURRENT_HIT_POINTS); const char* name = critter_name(a1); debug_printf("\n%s minHp = %d; curHp = %d", name, minimumHitPoints, currentHitPoints); } CritterCombatData* combatData = &(a1->data.critter.combat); if ((combatData->maneuver & CRITTER_MANUEVER_FLEEING) != 0 || (combatData->results & ai->hurt_too_much) != 0 || critterGetStat(a1, STAT_CURRENT_HIT_POINTS) < ai->min_hp) { const char* name = critter_name(a1); debug_printf("%s: FLEEING: I'm Hurt!", name); ai_run_away(a1, a2); return; } if (ai_check_drugs(a1)) { const char* name = critter_name(a1); debug_printf("%s: FLEEING: I need DRUGS!", name); ai_run_away(a1, a2); } else { if (a2 == NULL) { a2 = ai_danger_source(a1); } cai_perform_distance_prefs(a1, a2); if (a2 != NULL) { ai_try_attack(a1, a2); } } if (a2 != NULL && (a1->data.critter.combat.results & DAM_DEAD) == 0 && a1->data.critter.combat.ap != 0 && obj_dist(a1, a2) > ai->max_dist) { Object* v13 = combatAIInfoGetFriendlyDead(a1); if (v13 != NULL) { ai_move_away(a1, v13, 10); combatAIInfoSetFriendlyDead(a1, NULL); } else { int perception = critterGetStat(a1, STAT_PERCEPTION); if (!ai_find_friend(a1, perception * 2, 5)) { combatData->maneuver |= CRITTER_MANEUVER_STOP_ATTACKING; } } } if (a2 == NULL && !isPartyMember(a1)) { Object* whoHitMe = combatData->whoHitMe; if (whoHitMe != NULL) { if ((whoHitMe->data.critter.combat.results & DAM_DEAD) == 0 && combatData->damageLastTurn > 0) { Object* v16 = combatAIInfoGetFriendlyDead(a1); if (v16 != NULL) { ai_move_away(a1, v16, 10); combatAIInfoSetFriendlyDead(a1, NULL); } else { const char* name = critter_name(a1); debug_printf("%s: FLEEING: Somebody is shooting at me that I can't see!"); ai_run_away(a1, NULL); } } } } Object* v18 = combatAIInfoGetFriendlyDead(a1); if (v18 != NULL) { ai_move_away(a1, v18, 10); if (obj_dist(a1, v18) >= 10) { combatAIInfoSetFriendlyDead(a1, NULL); } } Object* v20; int v21 = 5; // 0x42B156 if (a1->data.critter.combat.team != 0) { v20 = ai_find_nearest_team_in_combat(a1, a1, 1); } else { v20 = obj_dude; if (isPartyMember(a1)) { // NOTE: Uninline int distance = ai_get_distance_pref_value(a1); if (distance != -1) { v21 = aiPartyMemberDistances[distance]; } } } if (a2 == NULL && v20 != NULL && obj_dist(a1, v20) > v21) { int v23 = obj_dist(a1, v20); ai_move_steps_closer(a1, v20, v23 - v21, 0); } else { if (a1->data.critter.combat.ap > 0) { debug_printf("\n>>>NOTE: %s had extra AP's to use!<<<", critter_name(a1)); cai_perform_distance_prefs(a1, a2); } } } // 0x42B3FC bool combatai_want_to_join(Object* a1) { process_bk(); if ((a1->flags & OBJECT_HIDDEN) != 0) { return false; } if (a1->elevation != obj_dude->elevation) { return false; } if ((a1->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) { return false; } if (a1->data.critter.combat.damageLastTurn > 0) { return true; } if (a1->sid != -1) { scr_set_objs(a1->sid, NULL, NULL); scr_set_ext_param(a1->sid, 5); exec_script_proc(a1->sid, SCRIPT_PROC_COMBAT); } if ((a1->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) != 0) { return true; } if ((a1->data.critter.combat.maneuver & CRITTER_MANEUVER_STOP_ATTACKING) == 0) { return false; } if ((a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) == 0) { return false; } if (ai_danger_source(a1) == NULL) { return false; } return true; } // 0x42B4A8 bool combatai_want_to_stop(Object* a1) { process_bk(); if ((a1->data.critter.combat.maneuver & CRITTER_MANEUVER_STOP_ATTACKING) != 0) { return true; } if ((a1->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) != 0) { return true; } if ((a1->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { return true; } Object* v4 = ai_danger_source(a1); return v4 == NULL || !is_within_perception(a1, v4); } // 0x42B504 int combatai_switch_team(Object* obj, int team) { if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { return 0; } obj->data.critter.combat.team = team; if (obj->data.critter.combat.whoHitMeCid == -1) { critter_set_who_hit_me(obj, NULL); debug_printf("\nError: CombatData found with invalid who_hit_me!"); return -1; } Object* whoHitMe = obj->data.critter.combat.whoHitMe; if (whoHitMe != NULL) { if (whoHitMe->data.critter.combat.team == team) { critter_set_who_hit_me(obj, NULL); } } combatAIInfoSetLastTarget(obj, NULL); if (isInCombat()) { bool outlineWasEnabled = obj->outline != 0 && (obj->outline & OUTLINE_DISABLED) == 0; obj_remove_outline(obj, NULL); int outlineType; if (obj->data.critter.combat.team == obj_dude->data.critter.combat.team) { outlineType = OUTLINE_TYPE_2; } else { outlineType = OUTLINE_TYPE_HOSTILE; } obj_outline_object(obj, outlineType, NULL); if (outlineWasEnabled) { Rect rect; obj_turn_on_outline(obj, &rect); tile_refresh_rect(&rect, obj->elevation); } } return 0; } // 0x42B5D4 int combat_ai_set_ai_packet(Object* object, int aiPacket) { if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return -1; } object->data.critter.combat.aiPacket = aiPacket; if (isPotentialPartyMember(object)) { Proto* proto; if (proto_ptr(object->pid, &proto) == -1) { return -1; } proto->critter.aiPacket = aiPacket; } return 0; } // combatai_msg // 0x42B634 int combatai_msg(Object* a1, Attack* attack, int type, int delay) { if (PID_TYPE(a1->pid) != OBJ_TYPE_CRITTER) { return -1; } bool combatTaunts = true; configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, &combatTaunts); if (!combatTaunts) { return -1; } if (a1 == obj_dude) { return -1; } if (a1->data.critter.combat.results & 0x81) { return -1; } AiPacket* ai = ai_cap(a1); debug_printf("%s is using %s packet with a %d%% chance to taunt\n", object_name(a1), ai->name, ai->chance); if (roll_random(1, 100) > ai->chance) { return -1; } int start; int end; char* string; switch (type) { case AI_MESSAGE_TYPE_RUN: start = ai->run.start; end = ai->run.end; string = attack_str; break; case AI_MESSAGE_TYPE_MOVE: start = ai->move.start; end = ai->move.end; string = attack_str; break; case AI_MESSAGE_TYPE_ATTACK: start = ai->attack.start; end = ai->attack.end; string = attack_str; break; case AI_MESSAGE_TYPE_MISS: start = ai->miss.start; end = ai->miss.end; string = target_str; break; case AI_MESSAGE_TYPE_HIT: start = ai->hit[attack->defenderHitLocation].start; end = ai->hit[attack->defenderHitLocation].end; string = target_str; break; default: return -1; } if (end < start) { return -1; } MessageListItem messageListItem; messageListItem.num = roll_random(start, end); if (!message_search(&ai_message_file, &messageListItem)) { debug_printf("\nERROR: combatai_msg: Couldn't find message # %d for %s", messageListItem.num, critter_name(a1)); return -1; } debug_printf("%s said message %d\n", object_name(a1), messageListItem.num); strncpy(string, messageListItem.text, 259); // TODO: Get rid of casts. return register_object_call(a1, (void*)type, (AnimationCallback*)ai_print_msg, delay); } // 0x42B80C static int ai_print_msg(Object* critter, int type) { if (text_object_count() > 0) { return 0; } char* string; switch (type) { case AI_MESSAGE_TYPE_HIT: case AI_MESSAGE_TYPE_MISS: string = target_str; break; default: string = attack_str; break; } AiPacket* ai = ai_cap(critter); Rect rect; if (text_object_create(critter, string, ai->font, ai->color, ai->outline_color, &rect) == 0) { tile_refresh_rect(&rect, critter->elevation); } return 0; } // Returns random critter for attacking as a result of critical weapon failure. // // 0x42B868 Object* combat_ai_random_target(Attack* attack) { // Looks like this function does nothing because it's result is not used. I // suppose it was planned to use range as a condition below, but it was // later moved into 0x426614, but remained here. item_w_range(attack->attacker, attack->hitMode); Object* critter = NULL; if (curr_crit_num != 0) { // Randomize starting critter. int start = roll_random(0, curr_crit_num - 1); int index = start; while (true) { Object* obj = curr_crit_list[index]; if (obj != attack->attacker && obj != attack->defender && can_see(attack->attacker, obj) && combat_check_bad_shot(attack->attacker, obj, attack->hitMode, false) == COMBAT_BAD_SHOT_OK) { critter = obj; break; } index += 1; if (index == curr_crit_num) { index = 0; } if (index == start) { break; } } } return critter; } // 0x42B90C static int combatai_rating(Object* obj) { int melee_damage; Object* item; int weapon_damage_min; int weapon_damage_max; if (obj == NULL) { return 0; } if (FID_TYPE(obj->fid) != OBJ_TYPE_CRITTER) { return 0; } if ((obj->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) { return 0; } melee_damage = critterGetStat(obj, STAT_MELEE_DAMAGE); item = inven_right_hand(obj); if (item != NULL && item_get_type(item) == ITEM_TYPE_WEAPON && item_w_damage_min_max(item, &weapon_damage_min, &weapon_damage_max) != -1 && melee_damage < weapon_damage_max) { melee_damage = weapon_damage_max; } item = inven_left_hand(obj); if (item != NULL && item_get_type(item) == ITEM_TYPE_WEAPON && item_w_damage_min_max(item, &weapon_damage_min, &weapon_damage_max) != -1 && melee_damage < weapon_damage_max) { melee_damage = weapon_damage_max; } return melee_damage + critterGetStat(obj, STAT_ARMOR_CLASS); } // 0x42B9D4 int combatai_check_retaliation(Object* a1, Object* a2) { Object* whoHitMe = a1->data.critter.combat.whoHitMe; if (whoHitMe != NULL) { int v3 = combatai_rating(a2); int result = combatai_rating(whoHitMe); if (v3 <= result) { return result; } } return critter_set_who_hit_me(a1, a2); } // 0x42BA04 bool is_within_perception(Object* a1, Object* a2) { if (a2 == NULL) { return false; } int distance = obj_dist(a2, a1); int perception = critterGetStat(a1, STAT_PERCEPTION); int sneak = skill_level(a2, SKILL_SNEAK); if (can_see(a1, a2)) { int maxDistance = perception * 5; if ((a2->flags & OBJECT_TRANS_GLASS) != 0) { maxDistance /= 2; } if (a2 == obj_dude) { if (is_pc_sneak_working()) { maxDistance /= 4; if (sneak > 120) { maxDistance -= 1; } } else if (is_pc_flag(DUDE_STATE_SNEAKING)) { maxDistance = maxDistance * 2 / 3; } } if (distance <= maxDistance) { return true; } } int maxDistance; if (isInCombat()) { maxDistance = perception * 2; } else { maxDistance = perception; } if (a2 == obj_dude) { if (is_pc_sneak_working()) { maxDistance /= 4; if (sneak > 120) { maxDistance -= 1; } } else if (is_pc_flag(DUDE_STATE_SNEAKING)) { maxDistance = maxDistance * 2 / 3; } } if (distance <= maxDistance) { return true; } return false; } // Load combatai.msg and apply language filter. // // 0x42BB34 static int combatai_load_messages() { if (!message_init(&ai_message_file)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "combatai.msg"); if (!message_load(&ai_message_file, path)) { return -1; } bool languageFilter; configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter); if (languageFilter) { message_filter(&ai_message_file); } return 0; } // NOTE: Inlined. // // 0x42BBD8 static int combatai_unload_messages() { if (!message_exit(&ai_message_file)) { return -1; } return 0; } // 0x42BBF0 void combatai_refresh_messages() { // 0x518220 static int old_state = -1; int languageFilter = 0; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter); if (languageFilter != old_state) { old_state = languageFilter; if (languageFilter == 1) { message_filter(&ai_message_file); } else { // NOTE: Uninline. combatai_unload_messages(); combatai_load_messages(); } } } // 0x42BC60 void combatai_notify_onlookers(Object* a1) { for (int index = 0; index < curr_crit_num; index++) { Object* obj = curr_crit_list[index]; if ((obj->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) == 0) { if (is_within_perception(obj, a1)) { obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01; if ((a1->data.critter.combat.results & DAM_DEAD) != 0) { if (!is_within_perception(obj, obj->data.critter.combat.whoHitMe)) { debug_printf("\nSomebody Died and I don't know why! Run!!!"); combatAIInfoSetFriendlyDead(obj, a1); } } } } } } // 0x42BCD4 void combatai_notify_friends(Object* a1) { int team = a1->data.critter.combat.team; for (int index = 0; index < curr_crit_num; index++) { Object* obj = curr_crit_list[index]; if ((obj->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) == 0 && team == obj->data.critter.combat.team) { if (is_within_perception(obj, a1)) { obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01; } } } } // 0x42BD28 void combatai_delete_critter(Object* obj) { // TODO: Check entire function. for (int i = 0; i < curr_crit_num; i++) { if (obj == curr_crit_list[i]) { curr_crit_num--; curr_crit_list[i] = curr_crit_list[curr_crit_num]; curr_crit_list[curr_crit_num] = obj; break; } } } ================================================ FILE: src/game/combatai.h ================================================ #ifndef FALLOUT_GAME_COMBATAI_H_ #define FALLOUT_GAME_COMBATAI_H_ #include #include "game/combatai_defs.h" #include "game/combat_defs.h" #include "plib/db/db.h" #include "game/message.h" #include "game/object_types.h" #include "game/party.h" #define AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT (3) typedef enum AiMessageType { AI_MESSAGE_TYPE_RUN, AI_MESSAGE_TYPE_MOVE, AI_MESSAGE_TYPE_ATTACK, AI_MESSAGE_TYPE_MISS, AI_MESSAGE_TYPE_HIT, } AiMessageType; typedef struct AiMessageRange { int start; int end; } AiMessageRange; typedef struct AiPacket { char* name; int packet_num; int max_dist; int min_to_hit; int min_hp; int aggression; int hurt_too_much; int secondary_freq; int called_freq; int font; int color; int outline_color; int chance; AiMessageRange run; AiMessageRange move; AiMessageRange attack; AiMessageRange miss; AiMessageRange hit[HIT_LOCATION_SPECIFIC_COUNT]; int area_attack_mode; int run_away_mode; int best_weapon; int distance; int attack_who; int chem_use; int chem_primary_desire[AI_PACKET_CHEM_PRIMARY_DESIRE_COUNT]; int disposition; char* body_type; char* general_type; } AiPacket; typedef struct AiRetargetData { Object* source; Object* target; Object* critterList[100]; int ratingList[100]; int critterCount; int sourceTeam; int sourceRating; bool notSameTile; int* tiles; int currentTileIndex; int sourceIntelligence; } AiRetargetData; extern const char* area_attack_mode_strs[AREA_ATTACK_MODE_COUNT]; extern const char* attack_who_mode_strs[ATTACK_WHO_COUNT]; extern const char* weapon_pref_strs[BEST_WEAPON_COUNT]; extern const char* chem_use_mode_strs[CHEM_USE_COUNT]; extern const char* distance_pref_strs[DISTANCE_COUNT]; extern const char* run_away_mode_strs[RUN_AWAY_MODE_COUNT]; extern const char* disposition_strs[DISPOSITION_COUNT]; int combat_ai_init(); void combat_ai_reset(); int combat_ai_exit(); int combat_ai_load(File* stream); int combat_ai_save(File* stream); int combat_ai_num(); char* combat_ai_name(int packetNum); int ai_get_burst_value(Object* obj); int ai_get_run_away_value(Object* obj); int ai_get_weapon_pref_value(Object* obj); int ai_get_distance_pref_value(Object* obj); int ai_get_attack_who_value(Object* obj); int ai_get_chem_use_value(Object* obj); int ai_set_burst_value(Object* critter, int areaAttackMode); int ai_set_run_away_value(Object* obj, int run_away_mode); int ai_set_weapon_pref_value(Object* critter, int bestWeapon); int ai_set_distance_pref_value(Object* critter, int distance); int ai_set_attack_who_value(Object* critter, int attackWho); int ai_set_chem_use_value(Object* critter, int chemUse); int ai_get_disposition(Object* obj); int ai_set_disposition(Object* obj, int a2); int compare_strength(const void* p1, const void* p2); int compare_weakness(const void* p1, const void* p2); Object* ai_danger_source(Object* a1); int caiSetupTeamCombat(Object* a1, Object* a2); int caiTeamCombatInit(Object** a1, int a2); void caiTeamCombatExit(); Object* ai_search_inven_weap(Object* critter, int a2, Object* a3); Object* ai_search_inven_armor(Object* critter); int cAIPrepWeaponItem(Object* critter, Object* item); void cai_attempt_w_reload(Object* critter_obj, int a2); void combat_ai_begin(int a1, void* a2); void combat_ai_over(); void combat_ai(Object* a1, Object* a2); bool combatai_want_to_join(Object* a1); bool combatai_want_to_stop(Object* a1); int combatai_switch_team(Object* obj, int team); int combat_ai_set_ai_packet(Object* object, int aiPacket); int combatai_msg(Object* a1, Attack* attack, int a3, int a4); Object* combat_ai_random_target(Attack* attack); int combatai_check_retaliation(Object* a1, Object* a2); bool is_within_perception(Object* a1, Object* a2); void combatai_refresh_messages(); void combatai_notify_onlookers(Object* a1); void combatai_notify_friends(Object* a1); void combatai_delete_critter(Object* obj); #endif /* FALLOUT_GAME_COMBATAI_H_ */ ================================================ FILE: src/game/combatai_defs.h ================================================ #ifndef FALLOUT_GAME_COMBATAI_DEFS_H_ #define FALLOUT_GAME_COMBATAI_DEFS_H_ typedef enum AreaAttackMode { AREA_ATTACK_MODE_ALWAYS, AREA_ATTACK_MODE_SOMETIMES, AREA_ATTACK_MODE_BE_SURE, AREA_ATTACK_MODE_BE_CAREFUL, AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE, AREA_ATTACK_MODE_COUNT, } AreaAttackMode; typedef enum RunAwayMode { RUN_AWAY_MODE_NONE, RUN_AWAY_MODE_COWARD, RUN_AWAY_MODE_FINGER_HURTS, RUN_AWAY_MODE_BLEEDING, RUN_AWAY_MODE_NOT_FEELING_GOOD, RUN_AWAY_MODE_TOURNIQUET, RUN_AWAY_MODE_NEVER, RUN_AWAY_MODE_COUNT, } RunAwayMode; typedef enum BestWeapon { BEST_WEAPON_NO_PREF, BEST_WEAPON_MELEE, BEST_WEAPON_MELEE_OVER_RANGED, BEST_WEAPON_RANGED_OVER_MELEE, BEST_WEAPON_RANGED, BEST_WEAPON_UNARMED, BEST_WEAPON_UNARMED_OVER_THROW, BEST_WEAPON_RANDOM, BEST_WEAPON_COUNT, } BestWeapon; typedef enum DistanceMode { DISTANCE_STAY_CLOSE, DISTANCE_CHARGE, DISTANCE_SNIPE, DISTANCE_ON_YOUR_OWN, DISTANCE_STAY, DISTANCE_COUNT, } DistanceMode; typedef enum AttackWho { ATTACK_WHO_WHOMEVER_ATTACKING_ME, ATTACK_WHO_STRONGEST, ATTACK_WHO_WEAKEST, ATTACK_WHO_WHOMEVER, ATTACK_WHO_CLOSEST, ATTACK_WHO_COUNT, } AttackWho; typedef enum ChemUse { CHEM_USE_CLEAN, CHEM_USE_STIMS_WHEN_HURT_LITTLE, CHEM_USE_STIMS_WHEN_HURT_LOTS, CHEM_USE_SOMETIMES, CHEM_USE_ANYTIME, CHEM_USE_ALWAYS, CHEM_USE_COUNT, } ChemUse; typedef enum Disposition { DISPOSITION_NONE, DISPOSITION_CUSTOM, DISPOSITION_COWARD, DISPOSITION_DEFENSIVE, DISPOSITION_AGGRESSIVE, DISPOSITION_BERKSERK, DISPOSITION_COUNT, } Disposition; typedef enum HurtTooMuch { HURT_BLIND, HURT_CRIPPLED, HURT_CRIPPLED_LEGS, HURT_CRIPPLED_ARMS, HURT_COUNT, } HurtTooMuch; #endif /* FALLOUT_GAME_COMBATAI_DEFS_H_ */ ================================================ FILE: src/game/config.c ================================================ #include "game/config.h" #include #include #include #include #include "plib/db/db.h" #include "plib/gnw/memory.h" #define CONFIG_FILE_MAX_LINE_LENGTH 256 // The initial number of sections (or key-value) pairs in the config. #define CONFIG_INITIAL_CAPACITY 10 static bool config_parse_line(Config* config, char* string); static bool config_split_line(char* string, char* key, char* value); static bool config_add_section(Config* config, const char* sectionKey); static bool config_strip_white_space(char* string); // 0x42BD90 bool config_init(Config* config) { if (config == NULL) { return false; } if (assoc_init(config, CONFIG_INITIAL_CAPACITY, sizeof(ConfigSection), NULL) != 0) { return false; } return true; } // 0x42BDBC void config_exit(Config* config) { if (config == NULL) { return; } for (int sectionIndex = 0; sectionIndex < config->size; sectionIndex++) { assoc_pair* sectionEntry = &(config->list[sectionIndex]); ConfigSection* section = (ConfigSection*)sectionEntry->data; for (int keyValueIndex = 0; keyValueIndex < section->size; keyValueIndex++) { assoc_pair* keyValueEntry = &(section->list[keyValueIndex]); char** value = (char**)keyValueEntry->data; mem_free(*value); *value = NULL; } assoc_free(section); } assoc_free(config); } // Parses command line argments and adds them into the config. // // The expected format of [argv] elements are "[section]key=value", otherwise // the element is silently ignored. // // NOTE: This function trims whitespace in key-value pair, but not in section. // I don't know if this is intentional or it's bug. // // 0x42BE38 bool config_cmd_line_parse(Config* config, int argc, char** argv) { if (config == NULL) { return false; } for (int arg = 0; arg < argc; arg++) { char* pch; char* string = argv[arg]; // Find opening bracket. pch = strchr(string, '['); if (pch == NULL) { continue; } char* sectionKey = pch + 1; // Find closing bracket. pch = strchr(sectionKey, ']'); if (pch == NULL) { continue; } *pch = '\0'; char key[260]; char value[260]; if (config_split_line(pch + 1, key, value)) { if (!config_set_string(config, sectionKey, key, value)) { *pch = ']'; return false; } } *pch = ']'; } return true; } // 0x42BF48 bool config_get_string(Config* config, const char* sectionKey, const char* key, char** valuePtr) { if (config == NULL || sectionKey == NULL || key == NULL || valuePtr == NULL) { return false; } int sectionIndex = assoc_search(config, sectionKey); if (sectionIndex == -1) { return false; } assoc_pair* sectionEntry = &(config->list[sectionIndex]); ConfigSection* section = (ConfigSection*)sectionEntry->data; int index = assoc_search(section, key); if (index == -1) { return false; } assoc_pair* keyValueEntry = &(section->list[index]); *valuePtr = *(char**)keyValueEntry->data; return true; } // 0x42BF90 bool config_set_string(Config* config, const char* sectionKey, const char* key, const char* value) { if (config == NULL || sectionKey == NULL || key == NULL || value == NULL) { return false; } int sectionIndex = assoc_search(config, sectionKey); if (sectionIndex == -1) { // FIXME: Looks like a bug, this function never returns -1, which will // eventually lead to crash. if (config_add_section(config, sectionKey) == -1) { return false; } sectionIndex = assoc_search(config, sectionKey); } assoc_pair* sectionEntry = &(config->list[sectionIndex]); ConfigSection* section = (ConfigSection*)sectionEntry->data; int index = assoc_search(section, key); if (index != -1) { assoc_pair* keyValueEntry = &(section->list[index]); char** existingValue = (char**)keyValueEntry->data; mem_free(*existingValue); *existingValue = NULL; assoc_delete(section, key); } char* valueCopy = mem_strdup(value); if (valueCopy == NULL) { return false; } if (assoc_insert(section, key, &valueCopy) == -1) { mem_free(valueCopy); return false; } return true; } // 0x42C05C bool config_get_value(Config* config, const char* sectionKey, const char* key, int* valuePtr) { if (valuePtr == NULL) { return false; } char* stringValue; if (!config_get_string(config, sectionKey, key, &stringValue)) { return false; } *valuePtr = atoi(stringValue); return true; } // 0x42C090 bool config_get_values(Config* config, const char* sectionKey, const char* key, int* arr, int count) { if (arr == NULL || count < 2) { return false; } char* string; if (!config_get_string(config, sectionKey, key, &string)) { return false; } char temp[CONFIG_FILE_MAX_LINE_LENGTH]; string = strncpy(temp, string, CONFIG_FILE_MAX_LINE_LENGTH - 1); while (1) { char* pch = strchr(string, ','); if (pch == NULL) { break; } count--; if (count == 0) { break; } *pch = '\0'; *arr++ = atoi(string); string = pch + 1; } if (count <= 1) { *arr = atoi(string); return true; } return false; } // 0x42C160 bool config_set_value(Config* config, const char* sectionKey, const char* key, int value) { char stringValue[20]; itoa(value, stringValue, 10); return config_set_string(config, sectionKey, key, stringValue); } // Reads .INI file into config. // // 0x42C280 bool config_load(Config* config, const char* filePath, bool isDb) { if (config == NULL || filePath == NULL) { return false; } char string[CONFIG_FILE_MAX_LINE_LENGTH]; if (isDb) { File* stream = db_fopen(filePath, "rb"); if (stream != NULL) { while (db_fgets(string, sizeof(string), stream) != NULL) { config_parse_line(config, string); } db_fclose(stream); } } else { FILE* stream = fopen(filePath, "rt"); if (stream != NULL) { while (fgets(string, sizeof(string), stream) != NULL) { config_parse_line(config, string); } fclose(stream); } // FIXME: This function returns `true` even if the file was not actually // read. I'm pretty sure it's bug. } return true; } // Writes config into .INI file. // // 0x42C324 bool config_save(Config* config, const char* filePath, bool isDb) { if (config == NULL || filePath == NULL) { return false; } if (isDb) { File* stream = db_fopen(filePath, "wt"); if (stream == NULL) { return false; } for (int sectionIndex = 0; sectionIndex < config->size; sectionIndex++) { assoc_pair* sectionEntry = &(config->list[sectionIndex]); db_fprintf(stream, "[%s]\n", sectionEntry->name); ConfigSection* section = (ConfigSection*)sectionEntry->data; for (int index = 0; index < section->size; index++) { assoc_pair* keyValueEntry = &(section->list[index]); db_fprintf(stream, "%s=%s\n", keyValueEntry->name, *(char**)keyValueEntry->data); } db_fprintf(stream, "\n"); } db_fclose(stream); } else { FILE* stream = fopen(filePath, "wt"); if (stream == NULL) { return false; } for (int sectionIndex = 0; sectionIndex < config->size; sectionIndex++) { assoc_pair* sectionEntry = &(config->list[sectionIndex]); fprintf(stream, "[%s]\n", sectionEntry->name); ConfigSection* section = (ConfigSection*)sectionEntry->data; for (int index = 0; index < section->size; index++) { assoc_pair* keyValueEntry = &(section->list[index]); fprintf(stream, "%s=%s\n", keyValueEntry->name, *(char**)keyValueEntry->data); } fprintf(stream, "\n"); } fclose(stream); } return true; } // Parses a line from .INI file into config. // // A line either contains a "[section]" section key or "key=value" pair. In the // first case section key is not added to config immediately, instead it is // stored in |section| for later usage. This prevents empty // sections in the config. // // In case of key-value pair it pretty straight forward - it adds key-value // pair into previously read section key stored in |section|. // // Returns `true` when a section was parsed or key-value pair was parsed and // added to the config, or `false` otherwise. // // 0x42C4BC static bool config_parse_line(Config* config, char* string) { // 0x518224 static char section[CONFIG_FILE_MAX_LINE_LENGTH] = "unknown"; char* pch; // Find comment marker and truncate the string. pch = strchr(string, ';'); if (pch != NULL) { *pch = '\0'; } // Find opening bracket. pch = strchr(string, '['); if (pch != NULL) { char* sectionKey = pch + 1; // Find closing bracket. pch = strchr(sectionKey, ']'); if (pch != NULL) { *pch = '\0'; strcpy(section, sectionKey); return config_strip_white_space(section); } } char key[260]; char value[260]; if (!config_split_line(string, key, value)) { return false; } return config_set_string(config, section, key, value); } // Splits "key=value" pair from [string] and copy appropriate parts into [key] // and [value] respectively. // // Both key and value are trimmed. // // 0x42C594 static bool config_split_line(char* string, char* key, char* value) { if (string == NULL || key == NULL || value == NULL) { return false; } // Find equals character. char* pch = strchr(string, '='); if (pch == NULL) { return false; } *pch = '\0'; strcpy(key, string); strcpy(value, pch + 1); *pch = '='; config_strip_white_space(key); config_strip_white_space(value); return true; } // Ensures the config has a section with specified key. // // Return `true` if section exists or it was successfully added, or `false` // otherwise. // // 0x42C638 static bool config_add_section(Config* config, const char* sectionKey) { if (config == NULL || sectionKey == NULL) { return false; } if (assoc_search(config, sectionKey) != -1) { // Section already exists, no need to do anything. return true; } ConfigSection section; if (assoc_init(§ion, CONFIG_INITIAL_CAPACITY, sizeof(char**), NULL) == -1) { return false; } if (assoc_insert(config, sectionKey, §ion) == -1) { return false; } return true; } // Removes leading and trailing whitespace from the specified string. // // 0x42C698 static bool config_strip_white_space(char* string) { if (string == NULL) { return false; } int length = strlen(string); if (length == 0) { return true; } // Starting from the end of the string, loop while it's a whitespace and // decrement string length. char* pch = string + length - 1; while (length != 0 && isspace(*pch)) { length--; pch--; } // pch now points to the last non-whitespace character. pch[1] = '\0'; // Starting from the beginning of the string loop while it's a whitespace // and decrement string length. pch = string; while (isspace(*pch)) { pch++; length--; } // pch now points for to the first non-whitespace character. memmove(string, pch, length + 1); return true; } // 0x42C718 bool config_get_double(Config* config, const char* sectionKey, const char* key, double* valuePtr) { if (valuePtr == NULL) { return false; } char* stringValue; if (!config_get_string(config, sectionKey, key, &stringValue)) { return false; } *valuePtr = strtod(stringValue, NULL); return true; } // 0x42C74C bool config_set_double(Config* config, const char* sectionKey, const char* key, double value) { char stringValue[32]; sprintf(stringValue, "%.6f", value); return config_set_string(config, sectionKey, key, stringValue); } // NOTE: Boolean-typed variant of [config_get_value]. bool configGetBool(Config* config, const char* sectionKey, const char* key, bool* valuePtr) { if (valuePtr == NULL) { return false; } int integerValue; if (!config_get_value(config, sectionKey, key, &integerValue)) { return false; } *valuePtr = integerValue != 0; return true; } // NOTE: Boolean-typed variant of [configGetInt]. bool configSetBool(Config* config, const char* sectionKey, const char* key, bool value) { return config_set_value(config, sectionKey, key, value ? 1 : 0); } ================================================ FILE: src/game/config.h ================================================ #ifndef FALLOUT_GAME_CONFIG_H_ #define FALLOUT_GAME_CONFIG_H_ #include #include "plib/assoc/assoc.h" // A representation of .INI file. // // It's implemented as a [assoc_array] whos keys are section names of .INI file, // and it's values are [ConfigSection] structs. typedef assoc_array Config; // Representation of .INI section. // // It's implemented as a [assoc_array] whos keys are names of .INI file // key-pair values, and it's values are pointers to strings (char**). typedef assoc_array ConfigSection; bool config_init(Config* config); void config_exit(Config* config); bool config_cmd_line_parse(Config* config, int argc, char** argv); bool config_get_string(Config* config, const char* sectionKey, const char* key, char** valuePtr); bool config_set_string(Config* config, const char* sectionKey, const char* key, const char* value); bool config_get_value(Config* config, const char* sectionKey, const char* key, int* valuePtr); bool config_get_values(Config* config, const char* section, const char* key, int* arr, int count); bool config_set_value(Config* config, const char* sectionKey, const char* key, int value); bool config_load(Config* config, const char* filePath, bool isDb); bool config_save(Config* config, const char* filePath, bool isDb); bool config_get_double(Config* config, const char* sectionKey, const char* key, double* valuePtr); bool config_set_double(Config* config, const char* sectionKey, const char* key, double value); // TODO: Remove. bool configGetBool(Config* config, const char* sectionKey, const char* key, bool* valuePtr); bool configSetBool(Config* config, const char* sectionKey, const char* key, bool value); #endif /* FALLOUT_GAME_CONFIG_H_ */ ================================================ FILE: src/game/counter.c ================================================ #include "game/counter.h" #include #include "plib/gnw/input.h" #include "plib/gnw/debug.h" static void counter(); // 0x518324 static int counter_is_on = 0; // 0x518328 static unsigned char count = 0; // 0x51832C static clock_t last_time = 0; // 0x518330 static CounterOutputFunc* counter_output_func; // 0x42C790 void counter_on(CounterOutputFunc* outputFunc) { if (!counter_is_on) { debug_printf("Turning on counter...\n"); add_bk_process(counter); counter_output_func = outputFunc; counter_is_on = 1; last_time = clock(); } } // 0x42C7D4 void counter_off() { if (counter_is_on) { remove_bk_process(counter); counter_is_on = 0; } } // 0x42C7F4 static void counter() { // 0x56D730 static clock_t this_time; count++; if (count == 0) { this_time = clock(); if (counter_output_func != NULL) { counter_output_func(256.0 / (this_time - last_time) / 100.0); } last_time = this_time; } } ================================================ FILE: src/game/counter.h ================================================ #ifndef FALLOUT_GAME_COUNTER_H_ #define FALLOUT_GAME_COUNTER_H_ typedef void(CounterOutputFunc)(double a1); void counter_on(CounterOutputFunc* outputFunc); void counter_off(); #endif /* FALLOUT_GAME_COUNTER_H_ */ ================================================ FILE: src/game/credits.c ================================================ #include "game/credits.h" #include #include "game/art.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/cycle.h" #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/gmouse.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/palette.h" #include "int/sound.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #define CREDITS_WINDOW_WIDTH 640 #define CREDITS_WINDOW_HEIGHT 480 #define CREDITS_WINDOW_SCROLLING_DELAY 38 static bool credits_get_next_line(char* dest, int* font, int* color); // 0x56D740 static File* credits_file; // 0x56D744 static int name_color; // 0x56D748 static int title_font; // 0x56D74C static int name_font; // 0x56D750 static int title_color; // 0x42C860 void credits(const char* filePath, int backgroundFid, bool useReversedStyle) { int oldFont = text_curr(); loadColorTable("color.pal"); if (useReversedStyle) { title_color = colorTable[18917]; name_font = 103; title_font = 104; name_color = colorTable[13673]; } else { title_color = colorTable[13673]; name_font = 104; title_font = 103; name_color = colorTable[18917]; } soundContinueAll(); char localizedPath[MAX_PATH]; if (message_make_path(localizedPath, filePath)) { credits_file = db_fopen(localizedPath, "rt"); if (credits_file != NULL) { soundContinueAll(); cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_NONE); bool cursorWasHidden = mouse_hidden(); if (cursorWasHidden) { mouse_show(); } int creditsWindowX = 0; int creditsWindowY = 0; int window = win_add(creditsWindowX, creditsWindowY, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, colorTable[0], 20); soundContinueAll(); if (window != -1) { unsigned char* windowBuffer = win_get_buf(window); if (windowBuffer != NULL) { unsigned char* backgroundBuffer = (unsigned char*)mem_malloc(CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT); if (backgroundBuffer) { soundContinueAll(); memset(backgroundBuffer, colorTable[0], CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT); if (backgroundFid != -1) { CacheEntry* backgroundFrmHandle; Art* frm = art_ptr_lock(backgroundFid, &backgroundFrmHandle); if (frm != NULL) { int width = art_frame_width(frm, 0, 0); int height = art_frame_length(frm, 0, 0); unsigned char* backgroundFrmData = art_frame_data(frm, 0, 0); buf_to_buf(backgroundFrmData, width, height, width, backgroundBuffer + CREDITS_WINDOW_WIDTH * ((CREDITS_WINDOW_HEIGHT - height) / 2) + (CREDITS_WINDOW_WIDTH - width) / 2, CREDITS_WINDOW_WIDTH); art_ptr_unlock(backgroundFrmHandle); } } unsigned char* intermediateBuffer = (unsigned char*)mem_malloc(CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT); if (intermediateBuffer != NULL) { memset(intermediateBuffer, 0, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT); text_font(title_font); int titleFontLineHeight = text_height(); text_font(name_font); int nameFontLineHeight = text_height(); int lineHeight = nameFontLineHeight + (titleFontLineHeight >= nameFontLineHeight ? titleFontLineHeight - nameFontLineHeight : 0); int stringBufferSize = CREDITS_WINDOW_WIDTH * lineHeight; unsigned char* stringBuffer = (unsigned char*)mem_malloc(stringBufferSize); if (stringBuffer != NULL) { buf_to_buf(backgroundBuffer, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, CREDITS_WINDOW_WIDTH, windowBuffer, CREDITS_WINDOW_WIDTH); win_draw(window); palette_fade_to(cmap); unsigned char* v40 = intermediateBuffer + CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH; char str[260]; int font; int color; unsigned int tick = 0; bool stop = false; while (credits_get_next_line(str, &font, &color)) { text_font(font); int v19 = text_width(str); if (v19 >= CREDITS_WINDOW_WIDTH) { continue; } memset(stringBuffer, 0, stringBufferSize); text_to_buf(stringBuffer, str, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_WIDTH, color); unsigned char* dest = intermediateBuffer + CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH + (CREDITS_WINDOW_WIDTH - v19) / 2; unsigned char* src = stringBuffer; for (int index = 0; index < lineHeight; index++) { if (get_input() != -1) { stop = true; break; } memmove(intermediateBuffer, intermediateBuffer + CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH); memcpy(dest, src, v19); buf_to_buf(backgroundBuffer, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, CREDITS_WINDOW_WIDTH, windowBuffer, CREDITS_WINDOW_WIDTH); trans_buf_to_buf(intermediateBuffer, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, CREDITS_WINDOW_WIDTH, windowBuffer, CREDITS_WINDOW_WIDTH); while (elapsed_time(tick) < CREDITS_WINDOW_SCROLLING_DELAY) { } tick = get_time(); win_draw(window); src += CREDITS_WINDOW_WIDTH; } if (stop) { break; } } if (!stop) { for (int index = 0; index < CREDITS_WINDOW_HEIGHT; index++) { if (get_input() != -1) { break; } memmove(intermediateBuffer, intermediateBuffer + CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH); memset(intermediateBuffer + CREDITS_WINDOW_WIDTH * CREDITS_WINDOW_HEIGHT - CREDITS_WINDOW_WIDTH, 0, CREDITS_WINDOW_WIDTH); buf_to_buf(backgroundBuffer, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, CREDITS_WINDOW_WIDTH, windowBuffer, CREDITS_WINDOW_WIDTH); trans_buf_to_buf(intermediateBuffer, CREDITS_WINDOW_WIDTH, CREDITS_WINDOW_HEIGHT, CREDITS_WINDOW_WIDTH, windowBuffer, CREDITS_WINDOW_WIDTH); while (elapsed_time(tick) < CREDITS_WINDOW_SCROLLING_DELAY) { } tick = get_time(); win_draw(window); } } mem_free(stringBuffer); } mem_free(intermediateBuffer); } mem_free(backgroundBuffer); } } soundContinueAll(); palette_fade_to(black_palette); soundContinueAll(); win_delete(window); } if (cursorWasHidden) { mouse_hide(); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); cycle_enable(); db_fclose(credits_file); } } text_font(oldFont); } // 0x42CE6C static bool credits_get_next_line(char* dest, int* font, int* color) { char string[256]; while (db_fgets(string, 256, credits_file)) { char* pch; if (string[0] == ';') { continue; } else if (string[0] == '@') { *font = title_font; *color = title_color; pch = string + 1; } else if (string[0] == '#') { *font = name_font; *color = colorTable[17969]; pch = string + 1; } else { *font = name_font; *color = name_color; pch = string; } strcpy(dest, pch); return true; } return false; } ================================================ FILE: src/game/credits.h ================================================ #ifndef CREDITS_H #define CREDITS_H #include void credits(const char* path, int fid, bool useReversedStyle); #endif /* CREDITS_H */ ================================================ FILE: src/game/critter.c ================================================ #include "game/critter.h" #include #include #include "game/anim.h" #include "game/editor.h" #include "game/combat.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/endgame.h" #include "game/game.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/party.h" #include "game/proto.h" #include "game/queue.h" #include "game/roll.h" #include "game/reaction.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/tile.h" #include "game/trait.h" #include "game/worldmap.h" static int get_rad_damage_level(Object* obj, void* data); static int clear_rad_damage(Object* obj, void* data); static void process_rads(Object* obj, int radiationLevel, bool direction); static int critter_kill_count_clear(); static int critterClearObjDrugs(Object* obj, void* data); // TODO: Remove. // 0x50141C char _aCorpse[] = "corpse"; // TODO: Remove. // 0x501494 char byte_501494[] = ""; // List of stats affected by radiation. // // The values of this list specify stats that can be affected by radiation. // The amount of penalty to every stat (identified by index) is stored // separately in [rad_bonus] per radiation level. // // The order of stats is important - primary stats must be at the top. See // [RADIATION_EFFECT_PRIMARY_STAT_COUNT] for more info. // // 0x518358 int rad_stat[RADIATION_EFFECT_COUNT] = { STAT_STRENGTH, STAT_PERCEPTION, STAT_ENDURANCE, STAT_CHARISMA, STAT_INTELLIGENCE, STAT_AGILITY, STAT_CURRENT_HIT_POINTS, STAT_HEALING_RATE, }; // Denotes how many primary stats at the top of [rad_stat] array. // These stats are used to determine if critter is alive after applying // radiation effects. #define RADIATION_EFFECT_PRIMARY_STAT_COUNT 6 // List of stat modifiers caused by radiation at different radiation levels. // // 0x518378 int rad_bonus[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT] = { // clang-format off { 0, 0, 0, 0, 0, 0, 0, 0 }, { -1, 0, 0, 0, 0, 0, 0, 0 }, { -1, 0, 0, 0, 0, -1, 0, -3 }, { -2, 0, -1, 0, 0, -2, -5, -5 }, { -4, -3, -3, -3, -1, -5, -15, -10 }, { -6, -5, -5, -5, -3, -6, -20, -10 }, // clang-format on }; // 0x518438 static Object* critterClearObj = NULL; // scrname.msg // // 0x56D754 static MessageList critter_scrmsg_file; // 0x56D75C static char pc_name[DUDE_NAME_MAX_LENGTH]; // 0x56D77C static int sneak_working; // 0x56D780 static int pc_kill_counts[KILL_TYPE_COUNT]; // Something with radiation. // // 0x56D7CC static int old_rad_level; // scrname_init // 0x42CF50 int critter_init() { critter_pc_reset_name(); // NOTE: Uninline. critter_kill_count_clear(); if (!message_init(&critter_scrmsg_file)) { debug_printf("\nError: Initing critter name message file!"); return -1; } char path[MAX_PATH]; sprintf(path, "%sscrname.msg", msg_path); if (!message_load(&critter_scrmsg_file, path)) { debug_printf("\nError: Loading critter name message file!"); return -1; } return 0; } // 0x42CFE4 void critter_reset() { critter_pc_reset_name(); // NOTE: Uninline; critter_kill_count_clear(); } // 0x42D004 void critter_exit() { message_exit(&critter_scrmsg_file); } // 0x42D01C int critter_load(File* stream) { if (db_freadInt(stream, &sneak_working) == -1) { return -1; } Proto* proto; proto_ptr(obj_dude->pid, &proto); return critter_read_data(stream, &(proto->critter.data)); } // 0x42D058 int critter_save(File* stream) { if (db_fwriteInt(stream, sneak_working) == -1) { return -1; } Proto* proto; proto_ptr(obj_dude->pid, &proto); return critter_write_data(stream, &(proto->critter.data)); } // 0x42D094 void critter_copy(CritterProtoData* dest, CritterProtoData* src) { memcpy(dest, src, sizeof(CritterProtoData)); } // 0x42D0A8 char* critter_name(Object* obj) { // TODO: Rename. // 0x51833C static char* _name_critter = _aCorpse; if (obj == obj_dude) { return pc_name; } if (obj->field_80 == -1) { if (obj->sid != -1) { Script* script; if (scr_ptr(obj->sid, &script) != -1) { obj->field_80 = script->field_14; } } } char* name = NULL; if (obj->field_80 != -1) { MessageListItem messageListItem; messageListItem.num = 101 + obj->field_80; if (message_search(&critter_scrmsg_file, &messageListItem)) { name = messageListItem.text; } } if (name == NULL || *name == '\0') { name = proto_name(obj->pid); } _name_critter = name; return name; } // 0x42D138 int critter_pc_set_name(const char* name) { if (strlen(name) <= DUDE_NAME_MAX_LENGTH) { strncpy(pc_name, name, DUDE_NAME_MAX_LENGTH); return 0; } return -1; } // 0x42D170 void critter_pc_reset_name() { strncpy(pc_name, "None", DUDE_NAME_MAX_LENGTH); } // 0x42D18C int critter_get_hits(Object* critter) { return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.hp : 0; } // 0x42D1A4 int critter_adjust_hits(Object* critter, int hp) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return 0; } int maximumHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS); int newHp = critter->data.critter.hp + hp; critter->data.critter.hp = newHp; if (maximumHp >= newHp) { if (newHp <= 0 && (critter->data.critter.combat.results & DAM_DEAD) == 0) { critter_kill(critter, -1, true); } } else { critter->data.critter.hp = maximumHp; } return 0; } // 0x42D1F8 int critter_get_poison(Object* critter) { return PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER ? critter->data.critter.poison : 0; } // Adjust critter's current poison by specified amount. // // For unknown reason this function only works on dude. // // The [amount] can either be positive (adds poison) or negative (removes // poison). // // 0x42D210 int critter_adjust_poison(Object* critter, int amount) { MessageListItem messageListItem; if (critter != obj_dude) { return -1; } if (amount > 0) { // Take poison resistance into account. amount -= amount * critterGetStat(critter, STAT_POISON_RESISTANCE) / 100; } else { if (obj_dude->data.critter.poison <= 0) { // Critter is not poisoned and we're want to decrease it even // further, which makes no sense. return 0; } } int newPoison = critter->data.critter.poison + amount; if (newPoison > 0) { critter->data.critter.poison = newPoison; queue_clear_type(EVENT_TYPE_POISON, NULL); queue_add(10 * (505 - 5 * newPoison), obj_dude, NULL, EVENT_TYPE_POISON); // You have been poisoned! messageListItem.num = 3000; if (amount < 0) { // You feel a little better. messageListItem.num = 3002; } } else { critter->data.critter.poison = 0; // You feel better. messageListItem.num = 3003; } if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } if (critter == obj_dude) { refresh_box_bar_win(); } return 0; } // 0x42D318 int critter_check_poison(Object* obj, void* data) { if (obj != obj_dude) { return 0; } critter_adjust_poison(obj, -2); critter_adjust_hits(obj, -1); intface_update_hit_points(false); MessageListItem messageListItem; // You take damage from poison. messageListItem.num = 3001; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } // NOTE: Uninline. int hitPoints = critter_get_hits(obj); if (hitPoints > 5) { return 0; } return 1; } // 0x42D38C int critter_get_rads(Object* obj) { return PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER ? obj->data.critter.radiation : 0; } // 0x42D3A4 int critter_adjust_rads(Object* obj, int amount) { MessageListItem messageListItem; if (obj != obj_dude) { return -1; } Proto* proto; proto_ptr(obj_dude->pid, &proto); if (amount > 0) { amount -= critterGetStat(obj, STAT_RADIATION_RESISTANCE) * amount / 100; } if (amount > 0) { proto->critter.data.flags |= CRITTER_BARTER; } if (amount > 0) { Object* geigerCounter = NULL; Object* item1 = inven_left_hand(obj_dude); if (item1 != NULL) { if (item1->pid == PROTO_ID_GEIGER_COUNTER_I || item1->pid == PROTO_ID_GEIGER_COUNTER_II) { geigerCounter = item1; } } Object* item2 = inven_right_hand(obj_dude); if (item2 != NULL) { if (item2->pid == PROTO_ID_GEIGER_COUNTER_I || item2->pid == PROTO_ID_GEIGER_COUNTER_II) { geigerCounter = item2; } } if (geigerCounter != NULL) { if (item_m_on(geigerCounter)) { if (amount > 5) { // The geiger counter is clicking wildly. messageListItem.num = 1009; } else { // The geiger counter is clicking. messageListItem.num = 1008; } if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } if (amount >= 10) { // You have received a large dose of radiation. messageListItem.num = 1007; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } obj->data.critter.radiation += amount; if (obj->data.critter.radiation < 0) { obj->data.critter.radiation = 0; } if (obj == obj_dude) { refresh_box_bar_win(); } return 0; } // 0x42D4F4 int critter_check_rads(Object* obj) { // Modifiers to endurance for performing radiation damage check. // // 0x518340 static int bonus[RADIATION_LEVEL_COUNT] = { 2, 0, -2, -4, -6, -8, }; if (obj != obj_dude) { return 0; } Proto* proto; proto_ptr(obj->pid, &proto); if ((proto->critter.data.flags & CRITTER_BARTER) == 0) { return 0; } old_rad_level = 0; queue_clear_type(EVENT_TYPE_RADIATION, get_rad_damage_level); // NOTE: Uninline int radiation = critter_get_rads(obj); int radiationLevel; if (radiation > 999) radiationLevel = RADIATION_LEVEL_FATAL; else if (radiation > 599) radiationLevel = RADIATION_LEVEL_DEADLY; else if (radiation > 399) radiationLevel = RADIATION_LEVEL_CRITICAL; else if (radiation > 199) radiationLevel = RADIATION_LEVEL_ADVANCED; else if (radiation > 99) radiationLevel = RADIATION_LEVEL_MINOR; else radiationLevel = RADIATION_LEVEL_NONE; if (stat_result(obj, STAT_ENDURANCE, bonus[radiationLevel], NULL) <= ROLL_FAILURE) { radiationLevel++; } if (radiationLevel > old_rad_level) { // Create timer event for applying radiation damage. RadiationEvent* radiationEvent = (RadiationEvent*)mem_malloc(sizeof(*radiationEvent)); if (radiationEvent == NULL) { return 0; } radiationEvent->radiationLevel = radiationLevel; radiationEvent->isHealing = 0; queue_add(GAME_TIME_TICKS_PER_HOUR * roll_random(4, 18), obj, radiationEvent, EVENT_TYPE_RADIATION); } proto->critter.data.flags &= ~(CRITTER_BARTER); return 0; } // 0x42D618 static int get_rad_damage_level(Object* obj, void* data) { RadiationEvent* radiationEvent = (RadiationEvent*)data; old_rad_level = radiationEvent->radiationLevel; return 0; } // 0x42D624 static int clear_rad_damage(Object* obj, void* data) { RadiationEvent* radiationEvent = (RadiationEvent*)data; if (radiationEvent->isHealing) { process_rads(obj, radiationEvent->radiationLevel, true); } return 1; } // Applies radiation. // // 0x42D63C static void process_rads(Object* obj, int radiationLevel, bool isHealing) { MessageListItem messageListItem; if (radiationLevel == RADIATION_LEVEL_NONE) { return; } int radiationLevelIndex = radiationLevel - 1; int modifier = isHealing ? -1 : 1; if (obj == obj_dude) { // Radiation level message, higher is worse. messageListItem.num = 1000 + radiationLevelIndex; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } for (int effect = 0; effect < RADIATION_EFFECT_COUNT; effect++) { int value = stat_get_bonus(obj, rad_stat[effect]); value += modifier * rad_bonus[radiationLevelIndex][effect]; stat_set_bonus(obj, rad_stat[effect], value); } if ((obj->data.critter.combat.results & DAM_DEAD) == 0) { // Loop thru effects affecting primary stats. If any of the primary stat // dropped below minimal value, kill it. for (int effect = 0; effect < RADIATION_EFFECT_PRIMARY_STAT_COUNT; effect++) { int base = stat_get_base(obj, rad_stat[effect]); int bonus = stat_get_bonus(obj, rad_stat[effect]); if (base + bonus < PRIMARY_STAT_MIN) { critter_kill(obj, -1, 1); break; } } } if ((obj->data.critter.combat.results & DAM_DEAD) != 0) { if (obj == obj_dude) { // You have died from radiation sickness. messageListItem.num = 1006; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } // 0x42D740 int critter_process_rads(Object* obj, void* data) { RadiationEvent* radiationEvent = (RadiationEvent*)data; if (!radiationEvent->isHealing) { // Schedule healing stats event in 7 days. RadiationEvent* newRadiationEvent = (RadiationEvent*)mem_malloc(sizeof(*newRadiationEvent)); if (newRadiationEvent != NULL) { queue_clear_type(EVENT_TYPE_RADIATION, clear_rad_damage); newRadiationEvent->radiationLevel = radiationEvent->radiationLevel; newRadiationEvent->isHealing = 1; queue_add(GAME_TIME_TICKS_PER_DAY * 7, obj, newRadiationEvent, EVENT_TYPE_RADIATION); } } process_rads(obj, radiationEvent->radiationLevel, radiationEvent->isHealing); return 1; } // 0x42D7A0 int critter_load_rads(File* stream, void** dataPtr) { RadiationEvent* radiationEvent = (RadiationEvent*)mem_malloc(sizeof(*radiationEvent)); if (radiationEvent == NULL) { return -1; } if (db_freadInt(stream, &(radiationEvent->radiationLevel)) == -1) goto err; if (db_freadInt(stream, &(radiationEvent->isHealing)) == -1) goto err; *dataPtr = radiationEvent; return 0; err: mem_free(radiationEvent); return -1; } // 0x42D7FC int critter_save_rads(File* stream, void* data) { RadiationEvent* radiationEvent = (RadiationEvent*)data; if (db_fwriteInt(stream, radiationEvent->radiationLevel) == -1) return -1; if (db_fwriteInt(stream, radiationEvent->isHealing) == -1) return -1; return 0; } // 0x42D82C int critter_get_base_damage_type(Object* obj) { if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { return 0; } Proto* proto; if (proto_ptr(obj->pid, &proto) == -1) { return 0; } return proto->critter.data.damageType; } // NOTE: Inlined. // // 0x42D860 static int critter_kill_count_clear() { memset(pc_kill_counts, 0, sizeof(pc_kill_counts)); return 0; } // 0x42D878 int critter_kill_count_inc(int killType) { if (killType != -1 && killType < KILL_TYPE_COUNT) { pc_kill_counts[killType]++; return 0; } return -1; } // 0x42D8A8 int critter_kill_count(int killType) { if (killType != -1 && killType < KILL_TYPE_COUNT) { return pc_kill_counts[killType]; } return 0; } // 0x42D8C0 int critter_kill_count_load(File* stream) { if (db_freadIntCount(stream, pc_kill_counts, KILL_TYPE_COUNT) == -1) { db_fclose(stream); return -1; } return 0; } // 0x42D8F0 int critter_kill_count_save(File* stream) { if (db_fwriteIntCount(stream, pc_kill_counts, KILL_TYPE_COUNT) == -1) { db_fclose(stream); return -1; } return 0; } // 0x42D920 int critterGetKillType(Object* obj) { if (obj == obj_dude) { int gender = critterGetStat(obj, STAT_GENDER); if (gender == GENDER_FEMALE) { return KILL_TYPE_WOMAN; } return KILL_TYPE_MAN; } if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { return -1; } Proto* proto; proto_ptr(obj->pid, &proto); return proto->critter.data.killType; } // 0x42D974 char* critter_kill_name(int killType) { if (killType != -1 && killType < KILL_TYPE_COUNT) { if (killType >= 0 && killType < KILL_TYPE_COUNT) { MessageListItem messageListItem; return getmsg(&proto_main_msg_file, &messageListItem, 1450 + killType); } else { return NULL; } } else { return byte_501494; } } // 0x42D9B4 char* critter_kill_info(int killType) { if (killType != -1 && killType < KILL_TYPE_COUNT) { if (killType >= 0 && killType < KILL_TYPE_COUNT) { MessageListItem messageListItem; return getmsg(&proto_main_msg_file, &messageListItem, 1469 + killType); } else { return NULL; } } else { return byte_501494; } } // 0x42D9F4 int critter_heal_hours(Object* critter, int a2) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return -1; } if (critter->data.critter.hp < critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS)) { critter_adjust_hits(critter, 14 * (a2 / 3)); } critter->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; return 0; } // 0x42DA54 static int critterClearObjDrugs(Object* obj, void* data) { return obj == critterClearObj; } // 0x42DA64 void critter_kill(Object* critter, int anim, bool a3) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return; } int elevation = critter->elevation; partyMemberRemove(critter); // NOTE: Original code uses goto to jump out from nested conditions below. bool shouldChangeFid = false; int fid; if (critter_is_prone(critter)) { int current = FID_ANIM_TYPE(critter->fid); if (current == ANIM_FALL_BACK || current == ANIM_FALL_FRONT) { bool back = false; if (current == ANIM_FALL_BACK) { back = true; } else { fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1); if (!art_exists(fid)) { back = true; } } if (back) { fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1); } shouldChangeFid = true; } } else { if (anim < 0) { anim = LAST_SF_DEATH_ANIM; } if (anim > LAST_SF_DEATH_ANIM) { debug_printf("\nError: Critter Kill: death_frame out of range!"); anim = LAST_SF_DEATH_ANIM; } fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1); obj_fix_violence_settings(&fid); if (!art_exists(fid)) { debug_printf("\nError: Critter Kill: Can't match fid!"); fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_BACK_BLOOD_SF, (critter->fid & 0xF000) >> 12, critter->rotation + 1); obj_fix_violence_settings(&fid); } shouldChangeFid = true; } Rect updatedRect; Rect tempRect; if (shouldChangeFid) { obj_set_frame(critter, 0, &updatedRect); obj_change_fid(critter, fid, &tempRect); rect_min_bound(&updatedRect, &tempRect, &updatedRect); } if (!critter_flag_check(critter->pid, CRITTER_FLAT)) { critter->flags |= OBJECT_NO_BLOCK; obj_toggle_flat(critter, &tempRect); } // NOTE: using uninitialized updatedRect/tempRect if fid was not set. rect_min_bound(&updatedRect, &tempRect, &updatedRect); obj_turn_off_light(critter, &tempRect); rect_min_bound(&updatedRect, &tempRect, &updatedRect); critter->data.critter.hp = 0; critter->data.critter.combat.results |= DAM_DEAD; if (critter->sid != -1) { scr_remove(critter->sid); critter->sid = -1; } critterClearObj = critter; queue_clear_type(EVENT_TYPE_DRUG, critterClearObjDrugs); item_destroy_all_hidden(critter); if (a3) { tile_refresh_rect(&updatedRect, elevation); } if (critter == obj_dude) { endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH); game_user_wants_to_quit = 2; } } // Returns experience for killing [critter]. // // 0x42DCB8 int critter_kill_exps(Object* critter) { Proto* proto; proto_ptr(critter->pid, &proto); return proto->critter.data.experience; } // 0x42DCDC bool critter_is_active(Object* critter) { if (critter == NULL) { return false; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return false; } if ((critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) != 0) { return false; } if ((critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD | DAM_LOSE_TURN)) != 0) { return false; } return (critter->data.critter.combat.results & DAM_DEAD) == 0; } // 0x42DD18 bool critter_is_dead(Object* critter) { if (critter == NULL) { return false; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return false; } if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) <= 0) { return true; } if ((critter->data.critter.combat.results & DAM_DEAD) != 0) { return true; } return false; } // 0x42DD58 bool critter_is_crippled(Object* critter) { if (critter == NULL) { return false; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return false; } return (critter->data.critter.combat.results & DAM_CRIP) != 0; } // 0x42DD80 bool critter_is_prone(Object* critter) { if (critter == NULL) { return false; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return false; } int anim = FID_ANIM_TYPE(critter->fid); return (critter->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0 || (anim >= FIRST_KNOCKDOWN_AND_DEATH_ANIM && anim <= LAST_KNOCKDOWN_AND_DEATH_ANIM) || (anim >= FIRST_SF_DEATH_ANIM && anim <= LAST_SF_DEATH_ANIM); } // critter_body_type // 0x42DDC4 int critter_body_type(Object* critter) { if (critter == NULL) { debug_printf("\nError: critter_body_type: pobj was NULL!"); return 0; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return 0; } Proto* proto; proto_ptr(critter->pid, &proto); return proto->critter.data.bodyType; } // NOTE: Unused. // // 0x42DE10 int critter_load_data(CritterProtoData* critterData, const char* path) { File* stream; stream = db_fopen(path, "rb"); if (stream == NULL) { return -1; } if (critter_read_data(stream, critterData) == -1) { db_fclose(stream); return -1; } db_fclose(stream); return 0; } // 0x42DE58 int pc_load_data(const char* path) { File* stream = db_fopen(path, "rb"); if (stream == NULL) { return -1; } Proto* proto; proto_ptr(obj_dude->pid, &proto); if (critter_read_data(stream, &(proto->critter.data)) == -1) { db_fclose(stream); return -1; } db_fread(pc_name, DUDE_NAME_MAX_LENGTH, 1, stream); if (skill_load(stream) == -1) { db_fclose(stream); return -1; } if (trait_load(stream) == -1) { db_fclose(stream); return -1; } if (db_freadInt(stream, &character_points) == -1) { db_fclose(stream); return -1; } proto->critter.data.baseStats[STAT_DAMAGE_RESISTANCE_EMP] = 100; proto->critter.data.bodyType = 0; proto->critter.data.experience = 0; proto->critter.data.killType = 0; db_fclose(stream); return 0; } // 0x42DF70 int critter_read_data(File* stream, CritterProtoData* critterData) { if (db_freadInt(stream, &(critterData->flags)) == -1) return -1; if (db_freadIntCount(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1; if (db_freadIntCount(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1; if (db_freadIntCount(stream, critterData->skills, SKILL_COUNT) == -1) return -1; if (db_freadInt(stream, &(critterData->bodyType)) == -1) return -1; if (db_freadInt(stream, &(critterData->experience)) == -1) return -1; if (db_freadInt(stream, &(critterData->killType)) == -1) return -1; // NOTE: For unknown reason damage type is not present in two protos: Sentry // Bot and Weak Brahmin. These two protos are 412 bytes, not 416. // // Given that only Floating Eye Bot, Floater, and Nasty Floater have // natural damage type other than normal, I think addition of natural // damage type as a feature was a last minute design decision. Most protos // were updated, but not all. Another suggestion is that some team member // used outdated toolset to build those two protos (mapper or whatever // they used to create protos in the first place). // // Regardless of the reason, damage type is considered optional by original // code as seen at 0x42E01B. if (db_freadInt(stream, &(critterData->damageType)) == -1) { critterData->damageType = DAMAGE_TYPE_NORMAL; } return 0; } // NOTE: Unused. // // 0x42E044 int critter_save_data(CritterProtoData* critterData, const char* path) { File* stream; stream = db_fopen(path, "wb"); if (stream == NULL) { return -1; } if (critter_write_data(stream, critterData) == -1) { db_fclose(stream); return -1; } db_fclose(stream); return 0; } // 0x42E08C int pc_save_data(const char* path) { File* stream = db_fopen(path, "wb"); if (stream == NULL) { return -1; } Proto* proto; proto_ptr(obj_dude->pid, &proto); if (critter_write_data(stream, &(proto->critter.data)) == -1) { db_fclose(stream); return -1; } db_fwrite(pc_name, DUDE_NAME_MAX_LENGTH, 1, stream); if (skill_save(stream) == -1) { db_fclose(stream); return -1; } if (trait_save(stream) == -1) { db_fclose(stream); return -1; } if (db_fwriteInt(stream, character_points) == -1) { db_fclose(stream); return -1; } db_fclose(stream); return 0; } // 0x42E174 int critter_write_data(File* stream, CritterProtoData* critterData) { if (db_fwriteInt(stream, critterData->flags) == -1) return -1; if (db_fwriteIntCount(stream, critterData->baseStats, SAVEABLE_STAT_COUNT) == -1) return -1; if (db_fwriteIntCount(stream, critterData->bonusStats, SAVEABLE_STAT_COUNT) == -1) return -1; if (db_fwriteIntCount(stream, critterData->skills, SKILL_COUNT) == -1) return -1; if (db_fwriteInt(stream, critterData->bodyType) == -1) return -1; if (db_fwriteInt(stream, critterData->experience) == -1) return -1; if (db_fwriteInt(stream, critterData->killType) == -1) return -1; if (db_fwriteInt(stream, critterData->damageType) == -1) return -1; return 0; } // 0x42E220 void pc_flag_off(int state) { Proto* proto; proto_ptr(obj_dude->pid, &proto); proto->critter.data.flags &= ~(1 << state); if (state == DUDE_STATE_SNEAKING) { queue_remove_this(obj_dude, EVENT_TYPE_SNEAK); } refresh_box_bar_win(); } // 0x42E26C void pc_flag_on(int state) { Proto* proto; proto_ptr(obj_dude->pid, &proto); proto->critter.data.flags |= (1 << state); if (state == DUDE_STATE_SNEAKING) { critter_sneak_check(NULL, NULL); } refresh_box_bar_win(); } // 0x42E2B0 void pc_flag_toggle(int state) { // NOTE: Uninline. if (is_pc_flag(state)) { pc_flag_off(state); } else { pc_flag_on(state); } } // 0x42E2F8 bool is_pc_flag(int state) { Proto* proto; proto_ptr(obj_dude->pid, &proto); return (proto->critter.data.flags & (1 << state)) != 0; } // 0x42E32C int critter_sneak_check(Object* obj, void* data) { int time; int sneak = skill_level(obj_dude, SKILL_SNEAK); if (skill_result(obj_dude, SKILL_SNEAK, 0, NULL) < ROLL_SUCCESS) { time = 600; sneak_working = false; if (sneak > 250) time = 100; else if (sneak > 200) time = 120; else if (sneak > 170) time = 150; else if (sneak > 135) time = 200; else if (sneak > 100) time = 300; else if (sneak > 80) time = 400; } else { time = 600; sneak_working = true; } queue_add(time, obj_dude, NULL, EVENT_TYPE_SNEAK); return 0; } // 0x42E3E4 int critter_sneak_clear(Object* obj, void* data) { pc_flag_off(DUDE_STATE_SNEAKING); return 1; } // Returns true if dude is really sneaking. // // 0x42E3F4 bool is_pc_sneak_working() { // NOTE: Uninline. if (is_pc_flag(DUDE_STATE_SNEAKING)) { return sneak_working; } return false; } // 0x42E424 int critter_wake_up(Object* obj, void* data) { if ((obj->data.critter.combat.results & DAM_DEAD) != 0) { return 0; } obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN); obj->data.critter.combat.results |= DAM_KNOCKED_DOWN; if (isInCombat()) { obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01; } else { dude_standup(obj); } return 0; } // 0x42E460 int critter_wake_clear(Object* obj, void* data) { if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { return 0; } if ((obj->data.critter.combat.results & DAM_DEAD) != 0) { return 0; } obj->data.critter.combat.results &= ~(DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN); int fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_STAND, (obj->fid & 0xF000) >> 12, obj->rotation + 1); obj_change_fid(obj, fid, 0); return 0; } // 0x42E4C0 int critter_set_who_hit_me(Object* a1, Object* a2) { if (a1 == NULL) { return -1; } if (a2 != NULL && FID_TYPE(a2->fid) != OBJ_TYPE_CRITTER) { return -1; } if (PID_TYPE(a1->pid) == OBJ_TYPE_CRITTER) { if (a2 == NULL || a1->data.critter.combat.team != a2->data.critter.combat.team || (stat_result(a1, STAT_INTELLIGENCE, -1, NULL) < 2 && (!isPartyMember(a1) || !isPartyMember(a2)))) { a1->data.critter.combat.whoHitMe = a2; if (a2 == obj_dude) { reaction_set(a1, -3); } } } return 0; } // 0x42E564 bool critter_can_obj_dude_rest() { bool v1 = false; if (!wmMapCanRestHere(map_elevation)) { v1 = true; } bool result = true; Object** critterList; int critterListLength = obj_create_list(-1, map_elevation, OBJ_TYPE_CRITTER, &critterList); // TODO: Check conditions in this loop. for (int index = 0; index < critterListLength; index++) { Object* critter = critterList[index]; if ((critter->data.critter.combat.results & DAM_DEAD) != 0) { continue; } if (critter == obj_dude) { continue; } if (critter->data.critter.combat.whoHitMe != obj_dude) { if (!v1 || critter->data.critter.combat.team == obj_dude->data.critter.combat.team) { continue; } } result = false; break; } if (critterListLength != 0) { obj_delete_list(critterList); } return result; } // 0x42E62C int critter_compute_ap_from_distance(Object* critter, int actionPoints) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return 0; } int flags = critter->data.critter.combat.results; if ((flags & DAM_CRIP_LEG_LEFT) != 0 && (flags & DAM_CRIP_LEG_RIGHT) != 0) { return 8 * actionPoints; } else if ((flags & DAM_CRIP_LEG_ANY) != 0) { return 4 * actionPoints; } else { return actionPoints; } } // 0x42E66C bool critterIsOverloaded(Object* critter) { int maxWeight = critterGetStat(critter, STAT_CARRY_WEIGHT); int currentWeight = item_total_weight(critter); return maxWeight < currentWeight; } // 0x42E690 bool critter_is_fleeing(Object* critter) { return critter != NULL ? (critter->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0 : false; } // Checks proto critter flag. // // 0x42E6AC bool critter_flag_check(int pid, int flag) { if (pid == -1) { return false; } if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) { return false; } Proto* proto; proto_ptr(pid, &proto); return (proto->critter.data.flags & flag) != 0; } // NOTE: Unused. // // 0x42E6F0 void critter_flag_set(int pid, int flag) { Proto* proto; if (pid == -1) { return; } if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) { return; } proto_ptr(pid, &proto); proto->critter.data.flags |= flag; } // NOTE: Unused. // // 0x42E71C void critter_flag_unset(int pid, int flag) { Proto* proto; if (pid == -1) { return; } if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) { return; } proto_ptr(pid, &proto); proto->critter.data.flags &= ~flag; } // NOTE: Unused. // // 0x42E74C void critter_flag_toggle(int pid, int flag) { Proto* proto; if (pid == -1) { return; } if (PID_TYPE(pid) != OBJ_TYPE_CRITTER) { return; } proto_ptr(pid, &proto); proto->critter.data.flags ^= flag; } ================================================ FILE: src/game/critter.h ================================================ #ifndef FALLOUT_GAME_CRITTER_H_ #define FALLOUT_GAME_CRITTER_H_ #include #include "plib/db/db.h" #include "game/object_types.h" #include "game/proto_types.h" // Maximum length of dude's name length. #define DUDE_NAME_MAX_LENGTH 32 // The number of effects caused by radiation. // // A radiation effect is an identifier and does not have it's own name. It's // stat is specified in [rad_stat], and it's amount is specified // in [rad_bonus] for every [RadiationLevel]. #define RADIATION_EFFECT_COUNT 8 // Radiation levels. // // The names of levels are taken from Fallout 3, comments from Fallout 2. typedef enum RadiationLevel { // Very nauseous. RADIATION_LEVEL_NONE, // Slightly fatigued. RADIATION_LEVEL_MINOR, // Vomiting does not stop. RADIATION_LEVEL_ADVANCED, // Hair is falling out. RADIATION_LEVEL_CRITICAL, // Skin is falling off. RADIATION_LEVEL_DEADLY, // Intense agony. RADIATION_LEVEL_FATAL, // The number of radiation levels. RADIATION_LEVEL_COUNT, } RadiationLevel; typedef enum DudeState { DUDE_STATE_SNEAKING = 0, DUDE_STATE_LEVEL_UP_AVAILABLE = 3, DUDE_STATE_ADDICTED = 4, } DudeState; extern int rad_stat[RADIATION_EFFECT_COUNT]; extern int rad_bonus[RADIATION_LEVEL_COUNT][RADIATION_EFFECT_COUNT]; int critter_init(); void critter_reset(); void critter_exit(); int critter_load(File* stream); int critter_save(File* stream); char* critter_name(Object* obj); void critter_copy(CritterProtoData* dest, CritterProtoData* src); int critter_pc_set_name(const char* name); void critter_pc_reset_name(); int critter_get_hits(Object* critter); int critter_adjust_hits(Object* critter, int hp); int critter_get_poison(Object* critter); int critter_adjust_poison(Object* obj, int amount); int critter_check_poison(Object* obj, void* data); int critter_get_rads(Object* critter); int critter_adjust_rads(Object* obj, int amount); int critter_check_rads(Object* critter); int critter_process_rads(Object* obj, void* data); int critter_load_rads(File* stream, void** dataPtr); int critter_save_rads(File* stream, void* data); int critter_get_base_damage_type(Object* critter); int critter_kill_count_inc(int killType); int critter_kill_count(int killType); int critter_kill_count_load(File* stream); int critter_kill_count_save(File* stream); int critterGetKillType(Object* critter); char* critter_kill_name(int killType); char* critter_kill_info(int killType); int critter_heal_hours(Object* obj, int a2); void critter_kill(Object* critter, int anim, bool a3); int critter_kill_exps(Object* critter); bool critter_is_active(Object* critter); bool critter_is_dead(Object* critter); bool critter_is_crippled(Object* critter); bool critter_is_prone(Object* critter); int critter_body_type(Object* critter); int critter_load_data(CritterProtoData* critterData, const char* path); int pc_load_data(const char* path); int critter_read_data(File* stream, CritterProtoData* critterData); int critter_save_data(CritterProtoData* critterData, const char* path); int pc_save_data(const char* path); int critter_write_data(File* stream, CritterProtoData* critterData); void pc_flag_off(int state); void pc_flag_on(int state); void pc_flag_toggle(int state); bool is_pc_flag(int state); int critter_sneak_check(Object* obj, void* data); int critter_sneak_clear(Object* obj, void* data); bool is_pc_sneak_working(); int critter_wake_up(Object* obj, void* data); int critter_wake_clear(Object* obj, void* data); int critter_set_who_hit_me(Object* a1, Object* a2); bool critter_can_obj_dude_rest(); int critter_compute_ap_from_distance(Object* critter, int a2); bool critterIsOverloaded(Object* critter); bool critter_is_fleeing(Object* a1); bool critter_flag_check(int pid, int flag); void critter_flag_set(int pid, int flag); void critter_flag_unset(int pid, int flag); void critter_flag_toggle(int pid, int flag); #endif /* FALLOUT_GAME_CRITTER_H_ */ ================================================ FILE: src/game/cycle.c ================================================ #include "game/cycle.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/gconfig.h" #include "game/palette.h" #define COLOR_CYCLE_PERIOD_SLOW 200U #define COLOR_CYCLE_PERIOD_MEDIUM 142U #define COLOR_CYCLE_PERIOD_FAST 100U #define COLOR_CYCLE_PERIOD_VERY_FAST 33U static void cycle_colors(); // 0x51843C static int cycle_speed_factor = 1; // TODO: Convert colors to RGB. // clang-format off // Green. // // 0x518440 unsigned char slime[12] = { 0, 108, 0, 11, 115, 7, 27, 123, 15, 43, 131, 27, }; // Light gray? // // 0x51844C unsigned char shoreline[18] = { 83, 63, 43, 75, 59, 43, 67, 55, 39, 63, 51, 39, 55, 47, 35, 51, 43, 35, }; // Orange. // // 0x51845E unsigned char fire_slow[15] = { 255, 0, 0, 215, 0, 0, 147, 43, 11, 255, 119, 0, 255, 59, 0, }; // Red. // // 0x51846D unsigned char fire_fast[15] = { 71, 0, 0, 123, 0, 0, 179, 0, 0, 123, 0, 0, 71, 0, 0, }; // Light blue. // // 0x51847C unsigned char monitors[15] = { 107, 107, 111, 99, 103, 127, 87, 107, 143, 0, 147, 163, 107, 187, 255, }; // clang-format on // 0x51848C static bool cycle_initialized = false; // 0x518490 static bool cycle_enabled = false; // 0x56D7D0 static unsigned int last_cycle_fast; // 0x56D7D4 static unsigned int last_cycle_slow; // 0x56D7D8 static unsigned int last_cycle_medium; // 0x56D7DC static unsigned int last_cycle_very_fast; // 0x42E780 void cycle_init() { if (cycle_initialized) { return; } bool colorCycling; if (!configGetBool(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_COLOR_CYCLING_KEY, &colorCycling)) { colorCycling = true; } if (!colorCycling) { return; } for (int index = 0; index < 12; index++) { slime[index] >>= 2; } for (int index = 0; index < 18; index++) { shoreline[index] >>= 2; } for (int index = 0; index < 15; index++) { fire_slow[index] >>= 2; } for (int index = 0; index < 15; index++) { fire_fast[index] >>= 2; } for (int index = 0; index < 15; index++) { monitors[index] >>= 2; } add_bk_process(cycle_colors); cycle_initialized = true; cycle_enabled = true; int cycleSpeedFactor; if (!config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY, &cycleSpeedFactor)) { cycleSpeedFactor = 1; } change_cycle_speed(cycleSpeedFactor); } // 0x42E8CC void cycle_reset() { if (cycle_initialized) { last_cycle_slow = 0; last_cycle_medium = 0; last_cycle_fast = 0; last_cycle_very_fast = 0; add_bk_process(cycle_colors); cycle_enabled = true; } } // 0x42E90C void cycle_exit() { if (cycle_initialized) { remove_bk_process(cycle_colors); cycle_initialized = false; cycle_enabled = false; } } // 0x42E930 void cycle_disable() { cycle_enabled = false; } // 0x42E93C void cycle_enable() { cycle_enabled = true; } // 0x42E948 bool cycle_is_enabled() { return cycle_enabled; } // 0x42E97C static void cycle_colors() { // 0x518494 static int slime_start = 0; // 0x518498 static int shoreline_start = 0; // 0x51849C static int fire_slow_start = 0; // 0x5184A0 static int fire_fast_start = 0; // 0x5184A4 static int monitors_start = 0; // 0x5184A8 static unsigned char bobber_red = 0; // 0x5184A9 static signed char bobber_diff = -4; if (!cycle_enabled) { return; } bool changed = false; unsigned char* palette = getSystemPalette(); unsigned int time = get_time(); if (elapsed_tocks(time, last_cycle_slow) >= COLOR_CYCLE_PERIOD_SLOW * cycle_speed_factor) { changed = true; last_cycle_slow = time; int paletteIndex = 229 * 3; for (int index = slime_start; index < 12; index++) { palette[paletteIndex++] = slime[index]; } for (int index = 0; index < slime_start; index++) { palette[paletteIndex++] = slime[index]; } slime_start -= 3; if (slime_start < 0) { slime_start = 9; } paletteIndex = 248 * 3; for (int index = shoreline_start; index < 18; index++) { palette[paletteIndex++] = shoreline[index]; } for (int index = 0; index < shoreline_start; index++) { palette[paletteIndex++] = shoreline[index]; } shoreline_start -= 3; if (shoreline_start < 0) { shoreline_start = 15; } paletteIndex = 238 * 3; for (int index = fire_slow_start; index < 15; index++) { palette[paletteIndex++] = fire_slow[index]; } for (int index = 0; index < fire_slow_start; index++) { palette[paletteIndex++] = fire_slow[index]; } fire_slow_start -= 3; if (fire_slow_start < 0) { fire_slow_start = 12; } } if (elapsed_tocks(time, last_cycle_medium) >= COLOR_CYCLE_PERIOD_MEDIUM * cycle_speed_factor) { changed = true; last_cycle_medium = time; int paletteIndex = 243 * 3; for (int index = fire_fast_start; index < 15; index++) { palette[paletteIndex++] = fire_fast[index]; } for (int index = 0; index < fire_fast_start; index++) { palette[paletteIndex++] = fire_fast[index]; } fire_fast_start -= 3; if (fire_fast_start < 0) { fire_fast_start = 12; } } if (elapsed_tocks(time, last_cycle_fast) >= COLOR_CYCLE_PERIOD_FAST * cycle_speed_factor) { changed = true; last_cycle_fast = time; int paletteIndex = 233 * 3; for (int index = monitors_start; index < 15; index++) { palette[paletteIndex++] = monitors[index]; } for (int index = 0; index < monitors_start; index++) { palette[paletteIndex++] = monitors[index]; } monitors_start -= 3; if (monitors_start < 0) { monitors_start = 12; } } if (elapsed_tocks(time, last_cycle_very_fast) >= COLOR_CYCLE_PERIOD_VERY_FAST * cycle_speed_factor) { changed = true; last_cycle_very_fast = time; if (bobber_red == 0 || bobber_red == 60) { bobber_diff = -bobber_diff; } bobber_red += bobber_diff; int paletteIndex = 254 * 3; palette[paletteIndex++] = bobber_red; palette[paletteIndex++] = 0; palette[paletteIndex++] = 0; } if (changed) { palette_set_entries(palette + 229 * 3, 229, 255); } } // 0x42E950 void change_cycle_speed(int value) { cycle_speed_factor = value; config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY, value); } // NOTE: Unused. // // 0x42E974 int get_cycle_speed() { return cycle_speed_factor; } ================================================ FILE: src/game/cycle.h ================================================ #ifndef FALLOUT_GAME_CYCLE_H_ #define FALLOUT_GAME_CYCLE_H_ #include extern unsigned char slime[12]; extern unsigned char shoreline[18]; extern unsigned char fire_slow[15]; extern unsigned char fire_fast[15]; extern unsigned char monitors[15]; void cycle_init(); void cycle_reset(); void cycle_exit(); void cycle_disable(); void cycle_enable(); bool cycle_is_enabled(); void change_cycle_speed(int value); int get_cycle_speed(); #endif /* FALLOUT_GAME_CYCLE_H_ */ ================================================ FILE: src/game/diskspce.c ================================================ #include "game/diskspce.h" #include #include #include #include "game/gconfig.h" // 0x431560 int GetFreeDiskSpace(long* diskSpacePtr) { char* path; char drive[_MAX_DRIVE]; int useGetDrive; int driveNumber; struct diskfree_t df; *diskSpacePtr = 0; useGetDrive = 1; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_PATCHES_KEY, &path) == 1) { _splitpath(path, drive, NULL, NULL, NULL); if (drive[0] != '\0') { useGetDrive = 0; driveNumber = toupper(drive[0]) - 64; } } if (useGetDrive) { driveNumber = _getdrive(); } if (_getdiskfree(driveNumber, &df) == 0) { *diskSpacePtr = ((long)df.bytes_per_sector * (long)df.sectors_per_cluster * (long)df.avail_clusters) / 1024; return 0; } return -1; } ================================================ FILE: src/game/diskspce.h ================================================ #ifndef FALLOUT_GAME_DISKSPCE_H_ #define FALLOUT_GAME_DISKSPCE_H_ int GetFreeDiskSpace(long* diskSpacePtr); #endif /* FALLOUT_GAME_DISKSPCE_H_ */ ================================================ FILE: src/game/display.c ================================================ #include "game/display.h" #include #include #include "game/art.h" #include "plib/color/color.h" #include "game/combat.h" #include "plib/gnw/input.h" #include "plib/gnw/grbuf.h" #include "game/gmouse.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "plib/gnw/memory.h" #include "plib/gnw/text.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" // The maximum number of lines display monitor can hold. Once this value // is reached earlier messages are thrown away. #define DISPLAY_MONITOR_LINES_CAPACITY 100 // The maximum length of a string in display monitor (in characters). #define DISPLAY_MONITOR_LINE_LENGTH 80 #define DISPLAY_MONITOR_X 23 #define DISPLAY_MONITOR_Y 24 #define DISPLAY_MONITOR_WIDTH 167 #define DISPLAY_MONITOR_HEIGHT 60 #define DISPLAY_MONITOR_HALF_HEIGHT (DISPLAY_MONITOR_HEIGHT / 2) #define DISPLAY_MONITOR_FONT 101 #define DISPLAY_MONITOR_BEEP_DELAY 500U // 0x51850C static bool disp_init = false; // The rectangle that display monitor occupies in the main interface window. // // 0x518510 static Rect disp_rect = { DISPLAY_MONITOR_X, DISPLAY_MONITOR_Y, DISPLAY_MONITOR_X + DISPLAY_MONITOR_WIDTH - 1, DISPLAY_MONITOR_Y + DISPLAY_MONITOR_HEIGHT - 1, }; // 0x518520 static int dn_bid = -1; // 0x518524 static int up_bid = -1; // 0x56DBFC static char disp_str[DISPLAY_MONITOR_LINES_CAPACITY][DISPLAY_MONITOR_LINE_LENGTH]; // 0x56FB3C static unsigned char* disp_buf; // 0x56FB40 static int max_disp_ptr; // 0x56FB44 static bool display_enabled; // 0x56FB48 static int disp_curr; // 0x56FB4C static int intface_full_wid; // 0x56FB50 static int max_ptr; // 0x56FB54 static int disp_start; // 0x431610 int display_init() { if (!disp_init) { int oldFont = text_curr(); text_font(DISPLAY_MONITOR_FONT); max_ptr = DISPLAY_MONITOR_LINES_CAPACITY; max_disp_ptr = DISPLAY_MONITOR_HEIGHT / text_height(); disp_start = 0; disp_curr = 0; text_font(oldFont); disp_buf = (unsigned char*)mem_malloc(DISPLAY_MONITOR_WIDTH * DISPLAY_MONITOR_HEIGHT); if (disp_buf == NULL) { return -1; } CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 16, 0, 0, 0); Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle); if (backgroundFrm == NULL) { mem_free(disp_buf); return -1; } unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0); intface_full_wid = art_frame_width(backgroundFrm, 0, 0); buf_to_buf(backgroundFrmData + intface_full_wid * DISPLAY_MONITOR_Y + DISPLAY_MONITOR_X, DISPLAY_MONITOR_WIDTH, DISPLAY_MONITOR_HEIGHT, intface_full_wid, disp_buf, DISPLAY_MONITOR_WIDTH); art_ptr_unlock(backgroundFrmHandle); up_bid = win_register_button(interfaceWindow, DISPLAY_MONITOR_X, DISPLAY_MONITOR_Y, DISPLAY_MONITOR_WIDTH, DISPLAY_MONITOR_HALF_HEIGHT, -1, -1, -1, -1, NULL, NULL, NULL, 0); if (up_bid != -1) { win_register_button_func(up_bid, display_arrow_up, display_arrow_restore, display_scroll_up, NULL); } dn_bid = win_register_button(interfaceWindow, DISPLAY_MONITOR_X, DISPLAY_MONITOR_Y + DISPLAY_MONITOR_HALF_HEIGHT, DISPLAY_MONITOR_WIDTH, DISPLAY_MONITOR_HEIGHT - DISPLAY_MONITOR_HALF_HEIGHT, -1, -1, -1, -1, NULL, NULL, NULL, 0); if (dn_bid != -1) { win_register_button_func(dn_bid, display_arrow_down, display_arrow_restore, display_scroll_down, NULL); } display_enabled = true; disp_init = true; // NOTE: Uninline. display_clear(); } return 0; } // 0x431800 int display_reset() { // NOTE: Uninline. display_clear(); return 0; } // 0x43184C void display_exit() { if (disp_init) { mem_free(disp_buf); disp_init = false; } } // 0x43186C void display_print(char* str) { // 0x56FB58 static unsigned int last_time; if (!disp_init) { return; } int oldFont = text_curr(); text_font(DISPLAY_MONITOR_FONT); char knob = '\x95'; char knobString[2]; knobString[0] = knob; knobString[1] = '\0'; int knobWidth = text_width(knobString); if (!isInCombat()) { unsigned int now = get_bk_time(); if (elapsed_tocks(now, last_time) >= DISPLAY_MONITOR_BEEP_DELAY) { last_time = now; gsound_play_sfx_file("monitor"); } } // TODO: Refactor these two loops. char* v1 = NULL; while (true) { while (text_width(str) < DISPLAY_MONITOR_WIDTH - max_disp_ptr - knobWidth) { char* temp = disp_str[disp_start]; int length; if (knob != '\0') { *temp++ = knob; length = DISPLAY_MONITOR_LINE_LENGTH - 2; knob = '\0'; knobWidth = 0; } else { length = DISPLAY_MONITOR_LINE_LENGTH - 1; } strncpy(temp, str, length); disp_str[disp_start][DISPLAY_MONITOR_LINE_LENGTH - 1] = '\0'; disp_start = (disp_start + 1) % max_ptr; if (v1 == NULL) { text_font(oldFont); disp_curr = disp_start; display_redraw(); return; } str = v1 + 1; *v1 = ' '; v1 = NULL; } char* space = strrchr(str, ' '); if (space == NULL) { break; } if (v1 != NULL) { *v1 = ' '; } v1 = space; if (space != NULL) { *space = '\0'; } } char* temp = disp_str[disp_start]; int length; if (knob != '\0') { temp++; disp_str[disp_start][0] = knob; length = DISPLAY_MONITOR_LINE_LENGTH - 2; knob = '\0'; } else { length = DISPLAY_MONITOR_LINE_LENGTH - 1; } strncpy(temp, str, length); disp_str[disp_start][DISPLAY_MONITOR_LINE_LENGTH - 1] = '\0'; disp_start = (disp_start + 1) % max_ptr; text_font(oldFont); disp_curr = disp_start; display_redraw(); } // NOTE: Inlined. // // 0x431A2C void display_clear() { int index; if (disp_init) { for (index = 0; index < max_ptr; index++) { disp_str[index][0] = '\0'; } disp_start = 0; disp_curr = 0; display_redraw(); } } // 0x431A78 void display_redraw() { if (!disp_init) { return; } unsigned char* buf = win_get_buf(interfaceWindow); if (buf == NULL) { return; } buf += intface_full_wid * DISPLAY_MONITOR_Y + DISPLAY_MONITOR_X; buf_to_buf(disp_buf, DISPLAY_MONITOR_WIDTH, DISPLAY_MONITOR_HEIGHT, DISPLAY_MONITOR_WIDTH, buf, intface_full_wid); int oldFont = text_curr(); text_font(DISPLAY_MONITOR_FONT); for (int index = 0; index < max_disp_ptr; index++) { int stringIndex = (disp_curr + max_ptr + index - max_disp_ptr) % max_ptr; text_to_buf(buf + index * intface_full_wid * text_height(), disp_str[stringIndex], DISPLAY_MONITOR_WIDTH, intface_full_wid, colorTable[992]); // Even though the display monitor is rectangular, it's graphic is not. // To give a feel of depth it's covered by some metal canopy and // considered inclined outwards. This way earlier messages appear a // little bit far from player's perspective. To implement this small // detail the destination buffer is incremented by 1. buf++; } win_draw_rect(interfaceWindow, &disp_rect); text_font(oldFont); } // 0x431B70 void display_scroll_up(int btn, int keyCode) { if ((max_ptr + disp_curr - 1) % max_ptr != disp_start) { disp_curr = (max_ptr + disp_curr - 1) % max_ptr; display_redraw(); } } // 0x431B9C void display_scroll_down(int btn, int keyCode) { if (disp_curr != disp_start) { disp_curr = (disp_curr + 1) % max_ptr; display_redraw(); } } // 0x431BC8 void display_arrow_up(int btn, int keyCode) { gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_UP); } // 0x431BD4 void display_arrow_down(int btn, int keyCode) { gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_DOWN); } // 0x431BE0 void display_arrow_restore(int btn, int keyCode) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); } // 0x431BEC void display_disable() { if (display_enabled) { win_disable_button(dn_bid); win_disable_button(up_bid); display_enabled = false; } } // 0x431C14 void display_enable() { if (!display_enabled) { win_enable_button(dn_bid); win_enable_button(up_bid); display_enabled = true; } } ================================================ FILE: src/game/display.h ================================================ #ifndef FALLOUT_GAME_DISPLAY_H_ #define FALLOUT_GAME_DISPLAY_H_ int display_init(); int display_reset(); void display_exit(); void display_print(char* string); void display_clear(); void display_redraw(); void display_scroll_up(int btn, int keyCode); void display_scroll_down(int btn, int keyCode); void display_arrow_up(int btn, int keyCode); void display_arrow_down(int btn, int keyCode); void display_arrow_restore(int btn, int keyCode); void display_disable(); void display_enable(); #endif /* FALLOUT_GAME_DISPLAY_H_ */ ================================================ FILE: src/game/editor.c ================================================ #include "game/editor.h" #include #include #include #include #include "game/art.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "plib/db/db.h" #include "game/bmpdlog.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gmouse.h" #include "game/graphlib.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/palette.h" #include "game/perk.h" #include "game/proto.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/trait.h" #include "plib/gnw/gnw.h" #include "game/wordwrap.h" #include "game/worldmap.h" #define RENDER_ALL_STATS 7 #define EDITOR_WINDOW_X 0 #define EDITOR_WINDOW_Y 0 #define EDITOR_WINDOW_WIDTH 640 #define EDITOR_WINDOW_HEIGHT 480 #define NAME_BUTTON_X 9 #define NAME_BUTTON_Y 0 #define TAG_SKILLS_BUTTON_X 347 #define TAG_SKILLS_BUTTON_Y 26 #define TAG_SKILLS_BUTTON_CODE 536 #define PRINT_BTN_X 363 #define PRINT_BTN_Y 454 #define DONE_BTN_X 475 #define DONE_BTN_Y 454 #define CANCEL_BTN_X 571 #define CANCEL_BTN_Y 454 #define NAME_BTN_CODE 517 #define AGE_BTN_CODE 519 #define SEX_BTN_CODE 520 #define OPTIONAL_TRAITS_LEFT_BTN_X 23 #define OPTIONAL_TRAITS_RIGHT_BTN_X 298 #define OPTIONAL_TRAITS_BTN_Y 352 #define OPTIONAL_TRAITS_BTN_CODE 555 #define OPTIONAL_TRAITS_BTN_SPACE 2 #define SPECIAL_STATS_BTN_X 149 #define PERK_WINDOW_X 33 #define PERK_WINDOW_Y 91 #define PERK_WINDOW_WIDTH 573 #define PERK_WINDOW_HEIGHT 230 #define PERK_WINDOW_LIST_X 45 #define PERK_WINDOW_LIST_Y 43 #define PERK_WINDOW_LIST_WIDTH 192 #define PERK_WINDOW_LIST_HEIGHT 129 #define ANIMATE 0x01 #define RED_NUMBERS 0x02 #define BIG_NUM_WIDTH 14 #define BIG_NUM_HEIGHT 24 #define BIG_NUM_ANIMATION_DELAY 123 // TODO: Should be MAX(PERK_COUNT, TRAIT_COUNT). #define DIALOG_PICKER_NUM_OPTIONS PERK_COUNT typedef enum EditorFolder { EDITOR_FOLDER_PERKS, EDITOR_FOLDER_KARMA, EDITOR_FOLDER_KILLS, } EditorFolder; enum { EDITOR_DERIVED_STAT_ARMOR_CLASS, EDITOR_DERIVED_STAT_ACTION_POINTS, EDITOR_DERIVED_STAT_CARRY_WEIGHT, EDITOR_DERIVED_STAT_MELEE_DAMAGE, EDITOR_DERIVED_STAT_DAMAGE_RESISTANCE, EDITOR_DERIVED_STAT_POISON_RESISTANCE, EDITOR_DERIVED_STAT_RADIATION_RESISTANCE, EDITOR_DERIVED_STAT_SEQUENCE, EDITOR_DERIVED_STAT_HEALING_RATE, EDITOR_DERIVED_STAT_CRITICAL_CHANCE, EDITOR_DERIVED_STAT_COUNT, }; enum { EDITOR_FIRST_PRIMARY_STAT, EDITOR_HIT_POINTS = 43, EDITOR_POISONED, EDITOR_RADIATED, EDITOR_EYE_DAMAGE, EDITOR_CRIPPLED_RIGHT_ARM, EDITOR_CRIPPLED_LEFT_ARM, EDITOR_CRIPPLED_RIGHT_LEG, EDITOR_CRIPPLED_LEFT_LEG, EDITOR_FIRST_DERIVED_STAT, EDITOR_FIRST_SKILL = EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_COUNT, EDITOR_TAG_SKILL = EDITOR_FIRST_SKILL + SKILL_COUNT, EDITOR_SKILLS, EDITOR_OPTIONAL_TRAITS, EDITOR_FIRST_TRAIT, EDITOR_BUTTONS_COUNT = EDITOR_FIRST_TRAIT + TRAIT_COUNT, }; enum { EDITOR_GRAPHIC_BIG_NUMBERS, EDITOR_GRAPHIC_AGE_MASK, EDITOR_GRAPHIC_AGE_OFF, EDITOR_GRAPHIC_DOWN_ARROW_OFF, EDITOR_GRAPHIC_DOWN_ARROW_ON, EDITOR_GRAPHIC_NAME_MASK, EDITOR_GRAPHIC_NAME_ON, EDITOR_GRAPHIC_NAME_OFF, EDITOR_GRAPHIC_FOLDER_MASK, // mask for all three folders EDITOR_GRAPHIC_SEX_MASK, EDITOR_GRAPHIC_SEX_OFF, EDITOR_GRAPHIC_SEX_ON, EDITOR_GRAPHIC_SLIDER, // image containing small plus/minus buttons appeared near selected skill EDITOR_GRAPHIC_SLIDER_MINUS_OFF, EDITOR_GRAPHIC_SLIDER_MINUS_ON, EDITOR_GRAPHIC_SLIDER_PLUS_OFF, EDITOR_GRAPHIC_SLIDER_PLUS_ON, EDITOR_GRAPHIC_SLIDER_TRANS_MINUS_OFF, EDITOR_GRAPHIC_SLIDER_TRANS_MINUS_ON, EDITOR_GRAPHIC_SLIDER_TRANS_PLUS_OFF, EDITOR_GRAPHIC_SLIDER_TRANS_PLUS_ON, EDITOR_GRAPHIC_UP_ARROW_OFF, EDITOR_GRAPHIC_UP_ARROW_ON, EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP, EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN, EDITOR_GRAPHIC_AGE_ON, EDITOR_GRAPHIC_AGE_BOX, // image containing right and left buttons with age stepper in the middle EDITOR_GRAPHIC_ATTRIBOX, // ??? black image with two little arrows (up and down) in the right-top corner EDITOR_GRAPHIC_ATTRIBWN, // ??? not sure where and when it's used EDITOR_GRAPHIC_CHARWIN, // ??? looks like metal plate EDITOR_GRAPHIC_DONE_BOX, // metal plate holding DONE button EDITOR_GRAPHIC_FEMALE_OFF, EDITOR_GRAPHIC_FEMALE_ON, EDITOR_GRAPHIC_MALE_OFF, EDITOR_GRAPHIC_MALE_ON, EDITOR_GRAPHIC_NAME_BOX, // placeholder for name EDITOR_GRAPHIC_LEFT_ARROW_UP, EDITOR_GRAPHIC_LEFT_ARROW_DOWN, EDITOR_GRAPHIC_RIGHT_ARROW_UP, EDITOR_GRAPHIC_RIGHT_ARROW_DOWN, EDITOR_GRAPHIC_BARARRWS, // ??? two arrows up/down with some strange knob at the top, probably for scrollbar EDITOR_GRAPHIC_OPTIONS_BASE, // options metal plate EDITOR_GRAPHIC_OPTIONS_BUTTON_OFF, EDITOR_GRAPHIC_OPTIONS_BUTTON_ON, EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED, // all three folders with middle folder selected (karma) EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED, // all theee folders with right folder selected (kills) EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED, // all three folders with left folder selected (perks) EDITOR_GRAPHIC_KARMAFDR_PLACEOLDER, // ??? placeholder for traits folder image <- this is comment from intrface.lst EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF, EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON, EDITOR_GRAPHIC_COUNT, }; typedef struct KarmaEntry { int gvar; int art_num; int name; int description; } KarmaEntry; typedef struct GenericReputationEntry { int threshold; int name; } GenericReputationEntry; typedef struct PerkDialogOption { // Depending on the current mode this value is the id of either // perk, trait (handling Mutate perk), or skill (handling Tag perk). int value; char* name; } PerkDialogOption; // TODO: Field order is probably wrong. typedef struct KillInfo { const char* name; int killTypeId; int kills; } KillInfo; static int CharEditStart(); static void CharEditEnd(); static void RstrBckgProc(); static void DrawFolder(); static void list_perks(); static int kills_list_comp(const void* a1, const void* a2); static int ListKills(); static void PrintBigNum(int x, int y, int flags, int value, int previousValue, int windowHandle); static void PrintLevelWin(); static void PrintBasicStat(int stat, bool animate, int previousValue); static void PrintGender(); static void PrintAgeBig(); static void PrintBigname(); static void ListDrvdStats(); static void ListSkills(int a1); static void DrawInfoWin(); static int NameWindow(); static void PrintName(unsigned char* buf, int pitch); static int AgeWindow(); static void SexWindow(); static void StatButton(int eventCode); static int OptionWindow(); static int Save_as_ASCII(const char* fileName); static char* AddDots(char* string, int length); static void ResetScreen(); static void RegInfoAreas(); static int CheckValidPlayer(); static void SavePlayer(); static void RestorePlayer(); static int DrawCard(int graphicId, const char* name, const char* attributes, char* description); static void FldrButton(); static void InfoButton(int eventCode); static void SliderBtn(int a1); static int tagskl_free(); static void TagSkillSelect(int skill); static void ListTraits(); static int get_trait_count(); static void TraitSelect(int trait); static void list_karma(); static int UpdateLevel(); static void RedrwDPrks(); static int perks_dialog(); static int InputPDLoop(int count, void (*refreshProc)()); static int ListDPerks(); static bool GetMutateTrait(); static void RedrwDMTagSkl(); static bool Add4thTagSkill(); static void ListNewTagSkills(); static int ListMyTraits(int a1); static int name_sort_comp(const void* a1, const void* a2); static int DrawCard2(int frmId, const char* name, const char* rank, char* description); static void push_perks(); static void pop_perks(); static int PerkCount(); static int is_supper_bonus(); static int folder_init(); static void folder_exit(); static void folder_scroll(int direction); static void folder_clear(); static int folder_print_seperator(const char* string); static bool folder_print_line(const char* string); static bool folder_print_kill(const char* name, int kills); static int karma_vars_init(); static void karma_vars_exit(); static int karma_vars_qsort_compare(const void* a1, const void* a2); static int general_reps_init(); static void general_reps_exit(); static int general_reps_qsort_compare(const void* a1, const void* a2); // 0x431C40 static const int grph_id[EDITOR_GRAPHIC_COUNT] = { 170, 175, 176, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 8, 9, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 122, 123, 124, 125, 219, 220, 221, 222, 178, 179, 180, 38, 215, 216, }; // flags to preload fid // // 0x431D08 static const unsigned char copyflag[EDITOR_GRAPHIC_COUNT] = { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, }; // graphic ids for derived stats panel // // 0x431D3A static const short ndrvd[EDITOR_DERIVED_STAT_COUNT] = { 18, 19, 20, 21, 22, 23, 83, 24, 25, 26, }; // y offsets for stats +/- buttons // // 0x431D50 static const int StatYpos[7] = { 37, 70, 103, 136, 169, 202, 235, }; // stat ids for derived stats panel // // 0x431D6 static const short ndinfoxlt[EDITOR_DERIVED_STAT_COUNT] = { STAT_ARMOR_CLASS, STAT_MAXIMUM_ACTION_POINTS, STAT_CARRY_WEIGHT, STAT_MELEE_DAMAGE, STAT_DAMAGE_RESISTANCE, STAT_POISON_RESISTANCE, STAT_RADIATION_RESISTANCE, STAT_SEQUENCE, STAT_HEALING_RATE, STAT_CRITICAL_CHANCE, }; // TODO: Remove. // 0x431D93 char byte_431D93[64]; // TODO: Remove // 0x5016E4 char byte_5016E4[] = "------"; // 0x518528 static bool bk_enable = false; // 0x51852C static int skill_cursor = 0; // 0x518534 static int slider_y = 27; // 0x518538 int character_points = 0; // 0x51853C static KarmaEntry* karma_vars = NULL; // 0x518540 static int karma_vars_count = 0; // 0x518544 static GenericReputationEntry* general_reps = NULL; // 0x518548 static int general_reps_count = 0; // 0x51854C TownReputationEntry town_rep_info[TOWN_REPUTATION_COUNT] = { { GVAR_TOWN_REP_ARROYO, CITY_ARROYO }, { GVAR_TOWN_REP_KLAMATH, CITY_KLAMATH }, { GVAR_TOWN_REP_THE_DEN, CITY_DEN }, { GVAR_TOWN_REP_VAULT_CITY, CITY_VAULT_CITY }, { GVAR_TOWN_REP_GECKO, CITY_GECKO }, { GVAR_TOWN_REP_MODOC, CITY_MODOC }, { GVAR_TOWN_REP_SIERRA_BASE, CITY_SIERRA_ARMY_BASE }, { GVAR_TOWN_REP_BROKEN_HILLS, CITY_BROKEN_HILLS }, { GVAR_TOWN_REP_NEW_RENO, CITY_NEW_RENO }, { GVAR_TOWN_REP_REDDING, CITY_REDDING }, { GVAR_TOWN_REP_NCR, CITY_NEW_CALIFORNIA_REPUBLIC }, { GVAR_TOWN_REP_VAULT_13, CITY_VAULT_13 }, { GVAR_TOWN_REP_SAN_FRANCISCO, CITY_SAN_FRANCISCO }, { GVAR_TOWN_REP_ABBEY, CITY_ABBEY }, { GVAR_TOWN_REP_EPA, CITY_ENV_PROTECTION_AGENCY }, { GVAR_TOWN_REP_PRIMITIVE_TRIBE, CITY_PRIMITIVE_TRIBE }, { GVAR_TOWN_REP_RAIDERS, CITY_RAIDERS }, { GVAR_TOWN_REP_VAULT_15, CITY_VAULT_15 }, { GVAR_TOWN_REP_GHOST_FARM, CITY_MODOC_GHOST_TOWN }, }; // 0x5185E4 int addiction_vars[ADDICTION_REPUTATION_COUNT] = { GVAR_NUKA_COLA_ADDICT, GVAR_BUFF_OUT_ADDICT, GVAR_MENTATS_ADDICT, GVAR_PSYCHO_ADDICT, GVAR_RADAWAY_ADDICT, GVAR_ALCOHOL_ADDICT, GVAR_ADDICT_JET, GVAR_ADDICT_TRAGIC, }; // 0x518604 int addiction_pics[ADDICTION_REPUTATION_COUNT] = { 142, 126, 140, 144, 145, 52, 136, 149, }; // 0x518624 static int folder_up_button = -1; // 0x518628 static int folder_down_button = -1; // 0x56FB60 static char folder_card_string[256]; // 0x56FC60 static int skillsav[SKILL_COUNT]; // 0x56FCA8 static MessageList editor_message_file; // 0x56FCB0 static PerkDialogOption name_sort_list[DIALOG_PICKER_NUM_OPTIONS]; // buttons for selecting traits // // 0x5700A8 static int trait_bids[TRAIT_COUNT]; // 0x5700E8 static MessageListItem mesg; // 0x5700F8 static char old_str1[48]; // 0x570128 static char old_str2[48]; // buttons for tagging skills // // 0x570158 static int tag_bids[SKILL_COUNT]; // pc name // // 0x5701A0 static char name_save[32]; // 0x5701C0 static Size GInfo[EDITOR_GRAPHIC_COUNT]; // 0x570350 static CacheEntry* grph_key[EDITOR_GRAPHIC_COUNT]; // 0x570418 static unsigned char* grphcpy[EDITOR_GRAPHIC_COUNT]; // 0x5704E0 static unsigned char* grphbmp[EDITOR_GRAPHIC_COUNT]; // 0x5705A8 static int folder_max_lines; // 0x5705AC static int folder_line; // 0x5705B0 static int folder_card_fid; // 0x5705B4 static int folder_top_line; // 0x5705B8 static char* folder_card_title; // 0x5705BC static char* folder_card_title2; // 0x5705C0 static int folder_yoffset; // 0x5705C4 static int folder_karma_top_line; // 0x5705C8 static int folder_highlight_line; // 0x5705CC static char* folder_card_desc; // 0x5705D0 static int folder_ypos; // 0x5705D4 static int folder_kills_top_line; // 0x5705D8 static int folder_perk_top_line; // 0x5705DC static unsigned char* pbckgnd; // 0x5705E0 static int pwin; // 0x5705E4 static int SliderPlusID; // 0x5705E8 static int SliderNegID; // - stats buttons // // 0x5705EC static int stat_bids_minus[7]; // 0x570608 static unsigned char* win_buf; // 0x57060C static int edit_win; // + stats buttons // // 0x570610 static int stat_bids_plus[7]; // 0x57062C static unsigned char* pwin_buf; // 0x570630 static CritterProtoData dude_data; // 0x5707A4 static unsigned char* bckgnd; // 0x5707A8 static int cline; // 0x5707AC static int oldsline; // unspent skill points // // 0x5707B0 static int upsent_points_back; // 0x5707B4 static int last_level; // 0x5707B8 static int fontsave; // 0x5707BC static int kills_count; // character editor background // // 0x5707C0 static CacheEntry* bck_key; // current hit points // // 0x5707C4 static int hp_back; // 0x5707C8 static int mouse_ypos; // mouse y // 0x5707CC static int mouse_xpos; // mouse x // 0x5707D0 static int info_line; // 0x5707D4 static int folder; // 0x5707D8 static bool frstc_draw1; // 0x5707DC static int crow; // 0x5707E0 static bool frstc_draw2; // 0x5707E4 static int perk_back[PERK_COUNT]; // 0x5709C0 static unsigned int _repFtime; // 0x5709C4 static unsigned int _frame_time; // 0x5709C8 static int old_tags; // 0x5709CC static int last_level_back; // 0x5709E8 static int old_fid2; // 0x5709EC static int old_fid1; // 0x5709D0 static bool glblmode; // 0x5709D4 static int tag_skill_back[NUM_TAGGED_SKILLS]; // 0x5709F0 static int trait_back[3]; // current index for selecting new trait // // 0x5709FC static int trait_count; // 0x570A00 static int optrt_count; // 0x570A04 static int temp_trait[3]; // 0x570A10 static int tagskill_count; // 0x570A14 static int temp_tag_skill[NUM_TAGGED_SKILLS]; // 0x570A28 static char free_perk_back; // 0x570A29 static unsigned char free_perk; // 0x570A2A static unsigned char first_skill_list; // 0x431DF8 int editor_design(bool isCreationMode) { char* messageListItemText; char line1[128]; char line2[128]; const char* lines[] = { line2 }; glblmode = isCreationMode; SavePlayer(); if (CharEditStart() == -1) { debug_printf("\n ** Error loading character editor data! **\n"); return -1; } if (!glblmode) { if (UpdateLevel()) { stat_recalc_derived(obj_dude); ListTraits(); ListSkills(0); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); DrawInfoWin(); } } int rc = -1; while (rc == -1) { _frame_time = get_time(); int keyCode = get_input(); bool done = false; if (keyCode == 500) { done = true; } if (keyCode == KEY_RETURN || keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) { done = true; gsound_play_sfx_file("ib1p1xx1"); } if (done) { if (glblmode) { if (character_points != 0) { gsound_play_sfx_file("iisxxxx1"); // You must use all character points messageListItemText = getmsg(&editor_message_file, &mesg, 118); strcpy(line1, messageListItemText); // before starting the game! messageListItemText = getmsg(&editor_message_file, &mesg, 119); strcpy(line2, messageListItemText); dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0); win_draw(edit_win); rc = -1; continue; } if (tagskill_count > 0) { gsound_play_sfx_file("iisxxxx1"); // You must select all tag skills messageListItemText = getmsg(&editor_message_file, &mesg, 142); strcpy(line1, messageListItemText); // before starting the game! messageListItemText = getmsg(&editor_message_file, &mesg, 143); strcpy(line2, messageListItemText); dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0); win_draw(edit_win); rc = -1; continue; } if (is_supper_bonus()) { gsound_play_sfx_file("iisxxxx1"); // All stats must be between 1 and 10 messageListItemText = getmsg(&editor_message_file, &mesg, 157); strcpy(line1, messageListItemText); // before starting the game! messageListItemText = getmsg(&editor_message_file, &mesg, 158); strcpy(line2, messageListItemText); dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0); win_draw(edit_win); rc = -1; continue; } if (stricmp(critter_name(obj_dude), "None") == 0) { gsound_play_sfx_file("iisxxxx1"); // Warning: You haven't changed your player messageListItemText = getmsg(&editor_message_file, &mesg, 160); strcpy(line1, messageListItemText); // name. Use this character any way? messageListItemText = getmsg(&editor_message_file, &mesg, 161); strcpy(line2, messageListItemText); if (dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_YES_NO) == 0) { win_draw(edit_win); rc = -1; continue; } } } win_draw(edit_win); rc = 0; } else if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); win_draw(edit_win); } else if (keyCode == 502 || keyCode == KEY_ESCAPE || keyCode == KEY_UPPERCASE_C || keyCode == KEY_LOWERCASE_C || game_user_wants_to_quit != 0) { win_draw(edit_win); rc = 1; } else if (glblmode && (keyCode == 517 || keyCode == KEY_UPPERCASE_N || keyCode == KEY_LOWERCASE_N)) { NameWindow(); win_draw(edit_win); } else if (glblmode && (keyCode == 519 || keyCode == KEY_UPPERCASE_A || keyCode == KEY_LOWERCASE_A)) { AgeWindow(); win_draw(edit_win); } else if (glblmode && (keyCode == 520 || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S)) { SexWindow(); win_draw(edit_win); } else if (glblmode && (keyCode >= 503 && keyCode < 517)) { StatButton(keyCode); win_draw(edit_win); } else if ((glblmode && (keyCode == 501 || keyCode == KEY_UPPERCASE_O || keyCode == KEY_LOWERCASE_O)) || (!glblmode && (keyCode == 501 || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P))) { OptionWindow(); win_draw(edit_win); } else if (keyCode >= 525 && keyCode < 535) { InfoButton(keyCode); win_draw(edit_win); } else { switch (keyCode) { case KEY_TAB: if (info_line >= 0 && info_line < 7) { info_line = glblmode ? 82 : 7; } else if (info_line >= 7 && info_line < 9) { if (glblmode) { info_line = 82; } else { info_line = 10; folder = 0; } } else if (info_line >= 10 && info_line < 43) { switch (folder) { case EDITOR_FOLDER_PERKS: info_line = 10; folder = EDITOR_FOLDER_KARMA; break; case EDITOR_FOLDER_KARMA: info_line = 10; folder = EDITOR_FOLDER_KILLS; break; case EDITOR_FOLDER_KILLS: info_line = 43; break; } } else if (info_line >= 43 && info_line < 51) { info_line = 51; } else if (info_line >= 51 && info_line < 61) { info_line = 61; } else if (info_line >= 61 && info_line < 82) { info_line = 0; } else if (info_line >= 82 && info_line < 98) { info_line = 43; } PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListTraits(); ListSkills(0); PrintLevelWin(); DrawFolder(); ListDrvdStats(); DrawInfoWin(); win_draw(edit_win); break; case KEY_ARROW_LEFT: case KEY_MINUS: case KEY_UPPERCASE_J: if (info_line >= 0 && info_line < 7) { if (glblmode) { win_button_press_and_release(stat_bids_minus[info_line]); win_draw(edit_win); } } else if (info_line >= 61 && info_line < 79) { if (glblmode) { win_button_press_and_release(tag_bids[glblmode - 61]); win_draw(edit_win); } else { SliderBtn(keyCode); win_draw(edit_win); } } else if (info_line >= 82 && info_line < 98) { if (glblmode) { win_button_press_and_release(trait_bids[glblmode - 82]); win_draw(edit_win); } } break; case KEY_ARROW_RIGHT: case KEY_PLUS: case KEY_UPPERCASE_N: if (info_line >= 0 && info_line < 7) { if (glblmode) { win_button_press_and_release(stat_bids_plus[info_line]); win_draw(edit_win); } } else if (info_line >= 61 && info_line < 79) { if (glblmode) { win_button_press_and_release(tag_bids[glblmode - 61]); win_draw(edit_win); } else { SliderBtn(keyCode); win_draw(edit_win); } } else if (info_line >= 82 && info_line < 98) { if (glblmode) { win_button_press_and_release(trait_bids[glblmode - 82]); win_draw(edit_win); } } break; case KEY_ARROW_UP: if (info_line >= 10 && info_line < 43) { if (info_line == 10) { if (folder_top_line > 0) { folder_scroll(-1); info_line--; DrawFolder(); DrawInfoWin(); } } else { info_line--; DrawFolder(); DrawInfoWin(); } win_draw(edit_win); } else { switch (info_line) { case 0: info_line = 6; break; case 7: info_line = 9; break; case 43: info_line = 50; break; case 51: info_line = 60; break; case 61: info_line = 78; break; case 82: info_line = 97; break; default: info_line -= 1; break; } if (info_line >= 61 && info_line < 79) { skill_cursor = info_line - 61; } PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListTraits(); ListSkills(0); PrintLevelWin(); DrawFolder(); ListDrvdStats(); DrawInfoWin(); win_draw(edit_win); } break; case KEY_ARROW_DOWN: if (info_line >= 10 && info_line < 43) { if (info_line - 10 < folder_line - folder_top_line) { if (info_line - 10 == folder_max_lines - 1) { folder_scroll(1); } info_line++; DrawFolder(); DrawInfoWin(); } win_draw(edit_win); } else { switch (info_line) { case 6: info_line = 0; break; case 9: info_line = 7; break; case 50: info_line = 43; break; case 60: info_line = 51; break; case 78: info_line = 61; break; case 97: info_line = 82; break; default: info_line += 1; break; } if (info_line >= 61 && info_line < 79) { skill_cursor = info_line - 61; } PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListTraits(); ListSkills(0); PrintLevelWin(); DrawFolder(); ListDrvdStats(); DrawInfoWin(); win_draw(edit_win); } break; case 521: case 523: SliderBtn(keyCode); win_draw(edit_win); break; case 535: FldrButton(); win_draw(edit_win); break; case 17000: folder_scroll(-1); win_draw(edit_win); break; case 17001: folder_scroll(1); win_draw(edit_win); break; default: if (glblmode && (keyCode >= 536 && keyCode < 554)) { TagSkillSelect(keyCode - 536); win_draw(edit_win); } else if (glblmode && (keyCode >= 555 && keyCode < 571)) { TraitSelect(keyCode - 555); win_draw(edit_win); } else { if (keyCode == 390) { dump_screen(); } win_draw(edit_win); } } } } if (rc == 0) { if (isCreationMode) { proto_dude_update_gender(); palette_fade_to(black_palette); } } CharEditEnd(); if (rc == 1) { RestorePlayer(); } if (is_pc_flag(DUDE_STATE_LEVEL_UP_AVAILABLE)) { pc_flag_off(DUDE_STATE_LEVEL_UP_AVAILABLE); } intface_update_hit_points(false); return rc; } // 0x4329EC static int CharEditStart() { int i; char path[MAX_PATH]; int fid; char* str; int len; int btn; int x; int y; char perks[32]; char karma[32]; char kills[32]; fontsave = text_curr(); old_tags = 0; bk_enable = 0; old_fid2 = -1; old_fid1 = -1; frstc_draw2 = false; frstc_draw1 = false; first_skill_list = 1; old_str2[0] = '\0'; old_str1[0] = '\0'; text_font(101); slider_y = skill_cursor * (text_height() + 1) + 27; // skills skill_get_tags(temp_tag_skill, NUM_TAGGED_SKILLS); // NOTE: Uninline. tagskill_count = tagskl_free(); // traits trait_get(&(temp_trait[0]), &(temp_trait[1])); // NOTE: Uninline. trait_count = get_trait_count(); if (!glblmode) { bk_enable = map_disable_bk_processes(); } cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); if (!message_init(&editor_message_file)) { return -1; } sprintf(path, "%s%s", msg_path, "editor.msg"); if (!message_load(&editor_message_file, path)) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, (glblmode ? 169 : 177), 0, 0, 0); bckgnd = art_lock(fid, &bck_key, &(GInfo[0].width), &(GInfo[0].height)); if (bckgnd == NULL) { message_exit(&editor_message_file); return -1; } if (karma_vars_init() == -1) { return -1; } if (general_reps_init() == -1) { return -1; } soundContinueAll(); for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { fid = art_id(OBJ_TYPE_INTERFACE, grph_id[i], 0, 0, 0); grphbmp[i] = art_lock(fid, &(grph_key[i]), &(GInfo[i].width), &(GInfo[i].height)); if (grphbmp[i] == NULL) { break; } } if (i != EDITOR_GRAPHIC_COUNT) { while (--i >= 0) { art_ptr_unlock(grph_key[i]); } return -1; art_ptr_unlock(bck_key); message_exit(&editor_message_file); // NOTE: Uninline. RstrBckgProc(); return -1; } soundContinueAll(); for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { if (copyflag[i]) { grphcpy[i] = (unsigned char*)mem_malloc(GInfo[i].width * GInfo[i].height); if (grphcpy[i] == NULL) { break; } memcpy(grphcpy[i], grphbmp[i], GInfo[i].width * GInfo[i].height); } else { grphcpy[i] = (unsigned char*)-1; } } if (i != EDITOR_GRAPHIC_COUNT) { while (--i >= 0) { if (copyflag[i]) { mem_free(grphcpy[i]); } } for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { art_ptr_unlock(grph_key[i]); } art_ptr_unlock(bck_key); message_exit(&editor_message_file); // NOTE: Uninline. RstrBckgProc(); return -1; } int editorWindowX = EDITOR_WINDOW_X; int editorWindowY = EDITOR_WINDOW_Y; edit_win = win_add(editorWindowX, editorWindowY, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (edit_win == -1) { for (i = 0; i < EDITOR_GRAPHIC_COUNT; i++) { if (copyflag[i]) { mem_free(grphcpy[i]); } art_ptr_unlock(grph_key[i]); } art_ptr_unlock(bck_key); message_exit(&editor_message_file); // NOTE: Uninline. RstrBckgProc(); return -1; } win_buf = win_get_buf(edit_win); memcpy(win_buf, bckgnd, 640 * 480); if (glblmode) { text_font(103); // CHAR POINTS str = getmsg(&editor_message_file, &mesg, 116); text_to_buf(win_buf + (286 * 640) + 14, str, 640, 640, colorTable[18979]); PrintBigNum(126, 282, 0, character_points, 0, edit_win); // OPTIONS str = getmsg(&editor_message_file, &mesg, 101); text_to_buf(win_buf + (454 * 640) + 363, str, 640, 640, colorTable[18979]); // OPTIONAL TRAITS str = getmsg(&editor_message_file, &mesg, 139); text_to_buf(win_buf + (326 * 640) + 52, str, 640, 640, colorTable[18979]); PrintBigNum(522, 228, 0, optrt_count, 0, edit_win); // TAG SKILLS str = getmsg(&editor_message_file, &mesg, 138); text_to_buf(win_buf + (233 * 640) + 422, str, 640, 640, colorTable[18979]); PrintBigNum(522, 228, 0, tagskill_count, 0, edit_win); } else { text_font(103); str = getmsg(&editor_message_file, &mesg, 109); strcpy(perks, str); str = getmsg(&editor_message_file, &mesg, 110); strcpy(karma, str); str = getmsg(&editor_message_file, &mesg, 111); strcpy(kills, str); // perks selected len = text_width(perks); text_to_buf( grphcpy[46] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 61 - len / 2, perks, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, colorTable[18979]); len = text_width(karma); text_to_buf(grphcpy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 159 - len / 2, karma, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, colorTable[14723]); len = text_width(kills); text_to_buf(grphcpy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 257 - len / 2, kills, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, colorTable[14723]); // karma selected len = text_width(perks); text_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 61 - len / 2, perks, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, colorTable[14723]); len = text_width(karma); text_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 159 - len / 2, karma, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, colorTable[18979]); len = text_width(kills); text_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 257 - len / 2, kills, GInfo[46].width, GInfo[46].width, colorTable[14723]); // kills selected len = text_width(perks); text_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 61 - len / 2, perks, GInfo[46].width, GInfo[46].width, colorTable[14723]); len = text_width(karma); text_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 159 - len / 2, karma, GInfo[46].width, GInfo[46].width, colorTable[14723]); len = text_width(kills); text_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED] + 5 * GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width + 257 - len / 2, kills, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, colorTable[18979]); DrawFolder(); text_font(103); // PRINT str = getmsg(&editor_message_file, &mesg, 103); text_to_buf(win_buf + (EDITOR_WINDOW_WIDTH * PRINT_BTN_Y) + PRINT_BTN_X, str, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_WIDTH, colorTable[18979]); PrintLevelWin(); folder_init(); } text_font(103); // CANCEL str = getmsg(&editor_message_file, &mesg, 102); text_to_buf(win_buf + (EDITOR_WINDOW_WIDTH * CANCEL_BTN_Y) + CANCEL_BTN_X, str, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_WIDTH, colorTable[18979]); // DONE str = getmsg(&editor_message_file, &mesg, 100); text_to_buf(win_buf + (EDITOR_WINDOW_WIDTH * DONE_BTN_Y) + DONE_BTN_X, str, EDITOR_WINDOW_WIDTH, EDITOR_WINDOW_WIDTH, colorTable[18979]); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); if (!glblmode) { SliderPlusID = win_register_button( edit_win, 614, 20, GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].width, GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height, -1, 522, 521, 522, grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_OFF], grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_ON], 0, 96); SliderNegID = win_register_button( edit_win, 614, 20 + GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].height - 1, GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].width, GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_OFF].height, -1, 524, 523, 524, grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_OFF], grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_ON], 0, 96); win_register_button_sound_func(SliderPlusID, gsound_red_butt_press, NULL); win_register_button_sound_func(SliderNegID, gsound_red_butt_press, NULL); } ListSkills(0); DrawInfoWin(); soundContinueAll(); PrintBigname(); PrintAgeBig(); PrintGender(); if (glblmode) { x = NAME_BUTTON_X; btn = win_register_button( edit_win, x, NAME_BUTTON_Y, GInfo[EDITOR_GRAPHIC_NAME_ON].width, GInfo[EDITOR_GRAPHIC_NAME_ON].height, -1, -1, -1, NAME_BTN_CODE, grphcpy[EDITOR_GRAPHIC_NAME_OFF], grphcpy[EDITOR_GRAPHIC_NAME_ON], 0, 32); if (btn != -1) { win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_NAME_MASK]); win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL); } x += GInfo[EDITOR_GRAPHIC_NAME_ON].width; btn = win_register_button( edit_win, x, NAME_BUTTON_Y, GInfo[EDITOR_GRAPHIC_AGE_ON].width, GInfo[EDITOR_GRAPHIC_AGE_ON].height, -1, -1, -1, AGE_BTN_CODE, grphcpy[EDITOR_GRAPHIC_AGE_OFF], grphcpy[EDITOR_GRAPHIC_AGE_ON], 0, 32); if (btn != -1) { win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_AGE_MASK]); win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL); } x += GInfo[EDITOR_GRAPHIC_AGE_ON].width; btn = win_register_button( edit_win, x, NAME_BUTTON_Y, GInfo[EDITOR_GRAPHIC_SEX_ON].width, GInfo[EDITOR_GRAPHIC_SEX_ON].height, -1, -1, -1, SEX_BTN_CODE, grphcpy[EDITOR_GRAPHIC_SEX_OFF], grphcpy[EDITOR_GRAPHIC_SEX_ON], 0, 32); if (btn != -1) { win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_SEX_MASK]); win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL); } y = TAG_SKILLS_BUTTON_Y; for (i = 0; i < SKILL_COUNT; i++) { tag_bids[i] = win_register_button( edit_win, TAG_SKILLS_BUTTON_X, y, GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].width, GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height, -1, -1, -1, TAG_SKILLS_BUTTON_CODE + i, grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF], grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON], NULL, 32); y += GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height; } y = OPTIONAL_TRAITS_BTN_Y; for (i = 0; i < TRAIT_COUNT / 2; i++) { trait_bids[i] = win_register_button( edit_win, OPTIONAL_TRAITS_LEFT_BTN_X, y, GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].width, GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height, -1, -1, -1, OPTIONAL_TRAITS_BTN_CODE + i, grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF], grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON], NULL, 32); y += GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height + OPTIONAL_TRAITS_BTN_SPACE; } y = OPTIONAL_TRAITS_BTN_Y; for (i = TRAIT_COUNT / 2; i < TRAIT_COUNT; i++) { trait_bids[i] = win_register_button( edit_win, OPTIONAL_TRAITS_RIGHT_BTN_X, y, GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].width, GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height, -1, -1, -1, OPTIONAL_TRAITS_BTN_CODE + i, grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_OFF], grphbmp[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON], NULL, 32); y += GInfo[EDITOR_GRAPHIC_TAG_SKILL_BUTTON_ON].height + OPTIONAL_TRAITS_BTN_SPACE; } ListTraits(); } else { x = NAME_BUTTON_X; trans_buf_to_buf(grphcpy[EDITOR_GRAPHIC_NAME_OFF], GInfo[EDITOR_GRAPHIC_NAME_ON].width, GInfo[EDITOR_GRAPHIC_NAME_ON].height, GInfo[EDITOR_GRAPHIC_NAME_ON].width, win_buf + (EDITOR_WINDOW_WIDTH * NAME_BUTTON_Y) + x, EDITOR_WINDOW_WIDTH); x += GInfo[EDITOR_GRAPHIC_NAME_ON].width; trans_buf_to_buf(grphcpy[EDITOR_GRAPHIC_AGE_OFF], GInfo[EDITOR_GRAPHIC_AGE_ON].width, GInfo[EDITOR_GRAPHIC_AGE_ON].height, GInfo[EDITOR_GRAPHIC_AGE_ON].width, win_buf + (EDITOR_WINDOW_WIDTH * NAME_BUTTON_Y) + x, EDITOR_WINDOW_WIDTH); x += GInfo[EDITOR_GRAPHIC_AGE_ON].width; trans_buf_to_buf(grphcpy[EDITOR_GRAPHIC_SEX_OFF], GInfo[EDITOR_GRAPHIC_SEX_ON].width, GInfo[EDITOR_GRAPHIC_SEX_ON].height, GInfo[EDITOR_GRAPHIC_SEX_ON].width, win_buf + (EDITOR_WINDOW_WIDTH * NAME_BUTTON_Y) + x, EDITOR_WINDOW_WIDTH); btn = win_register_button(edit_win, 11, 327, GInfo[EDITOR_GRAPHIC_FOLDER_MASK].width, GInfo[EDITOR_GRAPHIC_FOLDER_MASK].height, -1, -1, -1, 535, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_mask(btn, grphbmp[EDITOR_GRAPHIC_FOLDER_MASK]); } } if (glblmode) { // +/- buttons for stats for (i = 0; i < 7; i++) { stat_bids_plus[i] = win_register_button(edit_win, SPECIAL_STATS_BTN_X, StatYpos[i], GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].width, GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height, -1, 518, 503 + i, 518, grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_OFF], grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_ON], NULL, 32); if (stat_bids_plus[i] != -1) { win_register_button_sound_func(stat_bids_plus[i], gsound_red_butt_press, NULL); } stat_bids_minus[i] = win_register_button(edit_win, SPECIAL_STATS_BTN_X, StatYpos[i] + GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height - 1, GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].width, GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].height, -1, 518, 510 + i, 518, grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_OFF], grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_ON], NULL, 32); if (stat_bids_minus[i] != -1) { win_register_button_sound_func(stat_bids_minus[i], gsound_red_butt_press, NULL); } } } RegInfoAreas(); soundContinueAll(); btn = win_register_button( edit_win, 343, 454, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 501, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button( edit_win, 552, 454, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 502, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], 0, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button( edit_win, 455, 454, GInfo[23].width, GInfo[23].height, -1, -1, -1, 500, grphbmp[23], grphbmp[24], 0, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } win_draw(edit_win); disable_box_bar_win(); return 0; } // 0x433AA8 static void CharEditEnd() { // NOTE: Uninline. folder_exit(); win_delete(edit_win); for (int index = 0; index < EDITOR_GRAPHIC_COUNT; index++) { art_ptr_unlock(grph_key[index]); if (copyflag[index]) { mem_free(grphcpy[index]); } } art_ptr_unlock(bck_key); // NOTE: Uninline. general_reps_exit(); // NOTE: Uninline. karma_vars_exit(); message_exit(&editor_message_file); intface_redraw(); // NOTE: Uninline. RstrBckgProc(); text_font(fontsave); if (glblmode == 1) { skill_set_tags(temp_tag_skill, 3); trait_set(temp_trait[0], temp_trait[1]); info_line = 0; critter_adjust_hits(obj_dude, 1000); } enable_box_bar_win(); } // NOTE: Inlined. // // 0x433BEC static void RstrBckgProc() { if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); } // CharEditInit // 0x433C0C void CharEditInit() { int i; info_line = 0; skill_cursor = 0; slider_y = 27; free_perk = 0; folder = EDITOR_FOLDER_PERKS; for (i = 0; i < 2; i++) { temp_trait[i] = -1; trait_back[i] = -1; } character_points = 5; last_level = 1; } // handle name input int get_input_str(int win, int cancelKeyCode, char* text, int maxLength, int x, int y, int textColor, int backgroundColor, int flags) { int cursorWidth = text_width("_") - 4; int windowWidth = win_width(win); int v60 = text_height(); unsigned char* windowBuffer = win_get_buf(win); if (maxLength > 255) { maxLength = 255; } char copy[257]; strcpy(copy, text); int nameLength = strlen(text); copy[nameLength] = ' '; copy[nameLength + 1] = '\0'; int nameWidth = text_width(copy); buf_fill(windowBuffer + windowWidth * y + x, nameWidth, text_height(), windowWidth, backgroundColor); text_to_buf(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor); win_draw(win); int blinkingCounter = 3; bool blink = false; int rc = 1; while (rc == 1) { _frame_time = get_time(); int keyCode = get_input(); if (keyCode == cancelKeyCode) { rc = 0; } else if (keyCode == KEY_RETURN) { gsound_play_sfx_file("ib1p1xx1"); rc = 0; } else if (keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) { rc = -1; } else { if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && nameLength >= 1) { buf_fill(windowBuffer + windowWidth * y + x, text_width(copy), v60, windowWidth, backgroundColor); copy[nameLength - 1] = ' '; copy[nameLength] = '\0'; text_to_buf(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor); nameLength--; win_draw(win); } else if ((keyCode >= KEY_FIRST_INPUT_CHARACTER && keyCode <= KEY_LAST_INPUT_CHARACTER) && nameLength < maxLength) { if ((flags & 0x01) != 0) { if (!isdoschar(keyCode)) { break; } } buf_fill(windowBuffer + windowWidth * y + x, text_width(copy), v60, windowWidth, backgroundColor); copy[nameLength] = keyCode & 0xFF; copy[nameLength + 1] = ' '; copy[nameLength + 2] = '\0'; text_to_buf(windowBuffer + windowWidth * y + x, copy, windowWidth, windowWidth, textColor); nameLength++; win_draw(win); } } blinkingCounter -= 1; if (blinkingCounter == 0) { blinkingCounter = 3; int color = blink ? backgroundColor : textColor; blink = !blink; buf_fill(windowBuffer + windowWidth * y + x + text_width(copy) - cursorWidth, cursorWidth, v60 - 2, windowWidth, color); } win_draw(win); while (elapsed_time(_frame_time) < 1000 / 24) { } } if (rc == 0 || nameLength > 0) { copy[nameLength] = '\0'; strcpy(text, copy); } return rc; } // 0x434060 bool isdoschar(int ch) { const char* punctuations = "#@!$`'~^&()-_=[]{}"; if (isalnum(ch)) { return true; } int length = strlen(punctuations); for (int index = 0; index < length; index++) { if (punctuations[index] == ch) { return true; } } return false; } // copy filename replacing extension // // 0x4340D0 char* strmfe(char* dest, const char* name, const char* ext) { char* save = dest; while (*name != '\0' && *name != '.') { *dest++ = *name++; } *dest++ = '.'; strcpy(dest, ext); return save; } // 0x43410C static void DrawFolder() { if (glblmode) { return; } buf_to_buf(bckgnd + (360 * 640) + 34, 280, 120, 640, win_buf + (360 * 640) + 34, 640); text_font(101); switch (folder) { case EDITOR_FOLDER_PERKS: buf_to_buf(grphcpy[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED], GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].height, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, win_buf + (327 * 640) + 11, 640); list_perks(); break; case EDITOR_FOLDER_KARMA: buf_to_buf(grphcpy[EDITOR_GRAPHIC_KARMA_FOLDER_SELECTED], GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].height, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, win_buf + (327 * 640) + 11, 640); list_karma(); break; case EDITOR_FOLDER_KILLS: buf_to_buf(grphcpy[EDITOR_GRAPHIC_KILLS_FOLDER_SELECTED], GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].height, GInfo[EDITOR_GRAPHIC_PERKS_FOLDER_SELECTED].width, win_buf + (327 * 640) + 11, 640); kills_count = ListKills(); break; default: debug_printf("\n ** Unknown folder type! **\n"); break; } } // 0x434238 static void list_perks() { const char* string; char perkName[80]; int perk; int perkLevel; bool hasContent = false; folder_clear(); if (temp_trait[0] != -1) { // TRAITS string = getmsg(&editor_message_file, &mesg, 156); if (folder_print_seperator(string)) { folder_card_fid = 54; // Optional Traits folder_card_title = getmsg(&editor_message_file, &mesg, 146); folder_card_title2 = NULL; // Optional traits describe your character in more detail. All traits will have positive and negative effects. You may choose up to two traits during creation. folder_card_desc = getmsg(&editor_message_file, &mesg, 147); hasContent = true; } if (temp_trait[0] != -1) { string = trait_name(temp_trait[0]); if (folder_print_line(string)) { folder_card_fid = trait_pic(temp_trait[0]); folder_card_title = trait_name(temp_trait[0]); folder_card_title2 = NULL; folder_card_desc = trait_description(temp_trait[0]); hasContent = true; } } if (temp_trait[1] != -1) { string = trait_name(temp_trait[1]); if (folder_print_line(string)) { folder_card_fid = trait_pic(temp_trait[1]); folder_card_title = trait_name(temp_trait[1]); folder_card_title2 = NULL; folder_card_desc = trait_description(temp_trait[1]); hasContent = true; } } } for (perk = 0; perk < PERK_COUNT; perk++) { if (perk_level(obj_dude, perk) != 0) { break; } } if (perk != PERK_COUNT) { // PERKS string = getmsg(&editor_message_file, &mesg, 109); folder_print_seperator(string); for (perk = 0; perk < PERK_COUNT; perk++) { perkLevel = perk_level(obj_dude, perk); if (perkLevel != 0) { string = perk_name(perk); if (perkLevel == 1) { strcpy(perkName, string); } else { sprintf(perkName, "%s (%d)", string, perkLevel); } if (folder_print_line(perkName)) { folder_card_fid = perk_skilldex_fid(perk); folder_card_title = perk_name(perk); folder_card_title2 = NULL; folder_card_desc = perk_description(perk); hasContent = true; } } } } if (!hasContent) { folder_card_fid = 71; // Perks folder_card_title = getmsg(&editor_message_file, &mesg, 124); folder_card_title2 = NULL; // Perks add additional abilities. Every third experience level, you can choose one perk. folder_card_desc = getmsg(&editor_message_file, &mesg, 127); } } // 0x434498 static int kills_list_comp(const void* a1, const void* a2) { const KillInfo* v1 = (const KillInfo*)a1; const KillInfo* v2 = (const KillInfo*)a2; return stricmp(v1->name, v2->name); } // 0x4344A4 static int ListKills() { int i; int killsCount; KillInfo kills[19]; int usedKills = 0; bool hasContent = false; folder_clear(); for (i = 0; i < KILL_TYPE_COUNT; i++) { killsCount = critter_kill_count(i); if (killsCount != 0) { KillInfo* killInfo = &(kills[usedKills]); killInfo->name = critter_kill_name(i); killInfo->killTypeId = i; killInfo->kills = killsCount; usedKills++; } } if (usedKills != 0) { qsort(kills, usedKills, sizeof(*kills), kills_list_comp); for (i = 0; i < usedKills; i++) { KillInfo* killInfo = &(kills[i]); if (folder_print_kill(killInfo->name, killInfo->kills)) { folder_card_fid = 46; folder_card_title = folder_card_string; folder_card_title2 = NULL; folder_card_desc = critter_kill_info(kills[i].killTypeId); sprintf(folder_card_string, "%s %s", killInfo->name, getmsg(&editor_message_file, &mesg, 126)); hasContent = true; } } } if (!hasContent) { folder_card_fid = 46; folder_card_title = getmsg(&editor_message_file, &mesg, 126); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, 129); } return usedKills; } // 0x4345DC static void PrintBigNum(int x, int y, int flags, int value, int previousValue, int windowHandle) { Rect rect; int windowWidth; unsigned char* windowBuf; int tens; int ones; unsigned char* tensBufferPtr; unsigned char* onesBufferPtr; unsigned char* numbersGraphicBufferPtr; windowWidth = win_width(windowHandle); windowBuf = win_get_buf(windowHandle); rect.ulx = x; rect.uly = y; rect.lrx = x + BIG_NUM_WIDTH * 2; rect.lry = y + BIG_NUM_HEIGHT; numbersGraphicBufferPtr = grphbmp[0]; if (flags & RED_NUMBERS) { // First half of the bignum.frm is white, // second half is red. numbersGraphicBufferPtr += GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width / 2; } tensBufferPtr = windowBuf + windowWidth * y + x; onesBufferPtr = tensBufferPtr + BIG_NUM_WIDTH; if (value >= 0 && value <= 99 && previousValue >= 0 && previousValue <= 99) { tens = value / 10; ones = value % 10; if (flags & ANIMATE) { if (previousValue % 10 != ones) { _frame_time = get_time(); buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 11, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, onesBufferPtr, windowWidth); win_draw_rect(windowHandle, &rect); while (elapsed_time(_frame_time) < BIG_NUM_ANIMATION_DELAY) ; } buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, onesBufferPtr, windowWidth); win_draw_rect(windowHandle, &rect); if (previousValue / 10 != tens) { _frame_time = get_time(); buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 11, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, tensBufferPtr, windowWidth); win_draw_rect(windowHandle, &rect); while (elapsed_time(_frame_time) < BIG_NUM_ANIMATION_DELAY) ; } buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, tensBufferPtr, windowWidth); win_draw_rect(windowHandle, &rect); } else { buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * tens, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, tensBufferPtr, windowWidth); buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * ones, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, onesBufferPtr, windowWidth); } } else { buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 9, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, tensBufferPtr, windowWidth); buf_to_buf(numbersGraphicBufferPtr + BIG_NUM_WIDTH * 9, BIG_NUM_WIDTH, BIG_NUM_HEIGHT, GInfo[EDITOR_GRAPHIC_BIG_NUMBERS].width, onesBufferPtr, windowWidth); } } // 0x434920 static void PrintLevelWin() { int color; int y; char* formattedValue; // NOTE: The length of this buffer is 8 bytes, which is enough to display // 999,999 (7 bytes NULL-terminated) experience points. Usually a player // will never gain that much during normal gameplay. // // However it's possible to use one of the F2 modding tools and savegame // editors to receive rediculous amount of experience points. Vanilla is // able to handle it, because `stringBuffer` acts as continuation of // `formattedValueBuffer`. This is not the case with MSVC, where // insufficient space for xp greater then 999,999 ruins the stack. In order // to fix the `formattedValueBuffer` is expanded to 16 bytes, so it should // be possible to store max 32-bit integer (4,294,967,295). char formattedValueBuffer[16]; char stringBuffer[128]; if (glblmode == 1) { return; } text_font(101); buf_to_buf(bckgnd + 640 * 280 + 32, 124, 32, 640, win_buf + 640 * 280 + 32, 640); // LEVEL y = 280; if (info_line != 7) { color = colorTable[992]; } else { color = colorTable[32747]; } int level = stat_pc_get(PC_STAT_LEVEL); sprintf(stringBuffer, "%s %d", getmsg(&editor_message_file, &mesg, 113), level); text_to_buf(win_buf + 640 * y + 32, stringBuffer, 640, 640, color); // EXPERIENCE y += text_height() + 1; if (info_line != 8) { color = colorTable[992]; } else { color = colorTable[32747]; } int exp = stat_pc_get(PC_STAT_EXPERIENCE); sprintf(stringBuffer, "%s %s", getmsg(&editor_message_file, &mesg, 114), itostndn(exp, formattedValueBuffer)); text_to_buf(win_buf + 640 * y + 32, stringBuffer, 640, 640, color); // EXP NEEDED TO NEXT LEVEL y += text_height() + 1; if (info_line != 9) { color = colorTable[992]; } else { color = colorTable[32747]; } int expToNextLevel = stat_pc_min_exp(); int expMsgId; if (expToNextLevel == -1) { expMsgId = 115; formattedValue = byte_5016E4; } else { expMsgId = 115; if (expToNextLevel > 999999) { expMsgId = 175; } formattedValue = itostndn(expToNextLevel, formattedValueBuffer); } sprintf(stringBuffer, "%s %s", getmsg(&editor_message_file, &mesg, expMsgId), formattedValue); text_to_buf(win_buf + 640 * y + 32, stringBuffer, 640, 640, color); } // 0x434B38 static void PrintBasicStat(int stat, bool animate, int previousValue) { int off; int color; const char* description; int value; int flags; int messageListItemId; text_font(101); if (stat == RENDER_ALL_STATS) { // NOTE: Original code is different, looks like tail recursion // optimization. for (stat = 0; stat < 7; stat++) { PrintBasicStat(stat, 0, 0); } return; } if (info_line == stat) { color = colorTable[32747]; } else { color = colorTable[992]; } off = 640 * (StatYpos[stat] + 8) + 103; // TODO: The original code is different. if (glblmode) { value = stat_get_base(obj_dude, stat) + stat_get_bonus(obj_dude, stat); flags = 0; if (animate) { flags |= ANIMATE; } if (value > 10) { flags |= RED_NUMBERS; } PrintBigNum(58, StatYpos[stat], flags, value, previousValue, edit_win); buf_to_buf(bckgnd + off, 40, text_height(), 640, win_buf + off, 640); messageListItemId = critterGetStat(obj_dude, stat) + 199; if (messageListItemId > 210) { messageListItemId = 210; } description = getmsg(&editor_message_file, &mesg, messageListItemId); text_to_buf(win_buf + 640 * (StatYpos[stat] + 8) + 103, description, 640, 640, color); } else { value = critterGetStat(obj_dude, stat); PrintBigNum(58, StatYpos[stat], 0, value, 0, edit_win); buf_to_buf(bckgnd + off, 40, text_height(), 640, win_buf + off, 640); value = critterGetStat(obj_dude, stat); if (value > 10) { value = 10; } description = stat_level_description(value); text_to_buf(win_buf + off, description, 640, 640, color); } } // 0x434F18 static void PrintGender() { int gender; char* str; char text[32]; int x, width; text_font(103); gender = critterGetStat(obj_dude, STAT_GENDER); str = getmsg(&editor_message_file, &mesg, 107 + gender); strcpy(text, str); width = GInfo[EDITOR_GRAPHIC_SEX_ON].width; x = (width / 2) - (text_width(text) / 2); memcpy(grphcpy[11], grphbmp[EDITOR_GRAPHIC_SEX_ON], width * GInfo[EDITOR_GRAPHIC_SEX_ON].height); memcpy(grphcpy[EDITOR_GRAPHIC_SEX_OFF], grphbmp[10], width * GInfo[EDITOR_GRAPHIC_SEX_OFF].height); x += 6 * width; text_to_buf(grphcpy[EDITOR_GRAPHIC_SEX_ON] + x, text, width, width, colorTable[14723]); text_to_buf(grphcpy[EDITOR_GRAPHIC_SEX_OFF] + x, text, width, width, colorTable[18979]); } // 0x43501C static void PrintAgeBig() { int age; char* str; char text[32]; int x, width; text_font(103); age = critterGetStat(obj_dude, STAT_AGE); str = getmsg(&editor_message_file, &mesg, 104); sprintf(text, "%s %d", str, age); width = GInfo[EDITOR_GRAPHIC_AGE_ON].width; x = (width / 2) + 1 - (text_width(text) / 2); memcpy(grphcpy[EDITOR_GRAPHIC_AGE_ON], grphbmp[EDITOR_GRAPHIC_AGE_ON], width * GInfo[EDITOR_GRAPHIC_AGE_ON].height); memcpy(grphcpy[EDITOR_GRAPHIC_AGE_OFF], grphbmp[EDITOR_GRAPHIC_AGE_OFF], width * GInfo[EDITOR_GRAPHIC_AGE_ON].height); x += 6 * width; text_to_buf(grphcpy[EDITOR_GRAPHIC_AGE_ON] + x, text, width, width, colorTable[14723]); text_to_buf(grphcpy[EDITOR_GRAPHIC_AGE_OFF] + x, text, width, width, colorTable[18979]); } // 0x435118 static void PrintBigname() { char* str; char text[32]; int x, width; char *pch, tmp; bool has_space; text_font(103); str = critter_name(obj_dude); strcpy(text, str); if (text_width(text) > 100) { pch = text; has_space = false; while (*pch != '\0') { tmp = *pch; *pch = '\0'; if (tmp == ' ') { has_space = true; } if (text_width(text) > 100) { break; } *pch = tmp; pch++; } if (has_space) { pch = text + strlen(text); while (pch != text && *pch != ' ') { *pch = '\0'; pch--; } } } width = GInfo[EDITOR_GRAPHIC_NAME_ON].width; x = (width / 2) + 3 - (text_width(text) / 2); memcpy(grphcpy[EDITOR_GRAPHIC_NAME_ON], grphbmp[EDITOR_GRAPHIC_NAME_ON], GInfo[EDITOR_GRAPHIC_NAME_ON].width * GInfo[EDITOR_GRAPHIC_NAME_ON].height); memcpy(grphcpy[EDITOR_GRAPHIC_NAME_OFF], grphbmp[EDITOR_GRAPHIC_NAME_OFF], GInfo[EDITOR_GRAPHIC_NAME_OFF].width * GInfo[EDITOR_GRAPHIC_NAME_OFF].height); x += 6 * width; text_to_buf(grphcpy[EDITOR_GRAPHIC_NAME_ON] + x, text, width, width, colorTable[14723]); text_to_buf(grphcpy[EDITOR_GRAPHIC_NAME_OFF] + x, text, width, width, colorTable[18979]); } // 0x43527C static void ListDrvdStats() { int conditions; int color; const char* messageListItemText; char t[420]; // TODO: Size is wrong. int y; conditions = obj_dude->data.critter.combat.results; text_font(101); y = 46; buf_to_buf(bckgnd + 640 * y + 194, 118, 108, 640, win_buf + 640 * y + 194, 640); // Hit Points if (info_line == EDITOR_HIT_POINTS) { color = colorTable[32747]; } else { color = colorTable[992]; } int currHp; int maxHp; if (glblmode) { maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); currHp = maxHp; } else { maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); currHp = critter_get_hits(obj_dude); } messageListItemText = getmsg(&editor_message_file, &mesg, 300); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); sprintf(t, "%d/%d", currHp, maxHp); text_to_buf(win_buf + 640 * y + 263, t, 640, 640, color); // Poisoned y += text_height() + 3; if (info_line == EDITOR_POISONED) { color = critter_get_poison(obj_dude) != 0 ? colorTable[32747] : colorTable[15845]; } else { color = critter_get_poison(obj_dude) != 0 ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 312); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); // Radiated y += text_height() + 3; if (info_line == EDITOR_RADIATED) { color = critter_get_rads(obj_dude) != 0 ? colorTable[32747] : colorTable[15845]; } else { color = critter_get_rads(obj_dude) != 0 ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 313); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); // Eye Damage y += text_height() + 3; if (info_line == EDITOR_EYE_DAMAGE) { color = (conditions & DAM_BLIND) ? colorTable[32747] : colorTable[15845]; } else { color = (conditions & DAM_BLIND) ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 314); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); // Crippled Right Arm y += text_height() + 3; if (info_line == EDITOR_CRIPPLED_RIGHT_ARM) { color = (conditions & DAM_CRIP_ARM_RIGHT) ? colorTable[32747] : colorTable[15845]; } else { color = (conditions & DAM_CRIP_ARM_RIGHT) ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 315); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); // Crippled Left Arm y += text_height() + 3; if (info_line == EDITOR_CRIPPLED_LEFT_ARM) { color = (conditions & DAM_CRIP_ARM_LEFT) ? colorTable[32747] : colorTable[15845]; } else { color = (conditions & DAM_CRIP_ARM_LEFT) ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 316); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); // Crippled Right Leg y += text_height() + 3; if (info_line == EDITOR_CRIPPLED_RIGHT_LEG) { color = (conditions & DAM_CRIP_LEG_RIGHT) ? colorTable[32747] : colorTable[15845]; } else { color = (conditions & DAM_CRIP_LEG_RIGHT) ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 317); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); // Crippled Left Leg y += text_height() + 3; if (info_line == EDITOR_CRIPPLED_LEFT_LEG) { color = (conditions & DAM_CRIP_LEG_LEFT) ? colorTable[32747] : colorTable[15845]; } else { color = (conditions & DAM_CRIP_LEG_LEFT) ? colorTable[992] : colorTable[1313]; } messageListItemText = getmsg(&editor_message_file, &mesg, 318); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); y = 179; buf_to_buf(bckgnd + 640 * y + 194, 116, 130, 640, win_buf + 640 * y + 194, 640); // Armor Class if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_ARMOR_CLASS) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 302); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); itoa(critterGetStat(obj_dude, STAT_ARMOR_CLASS), t, 10); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Action Points y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_ACTION_POINTS) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 301); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); itoa(critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS), t, 10); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Carry Weight y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_CARRY_WEIGHT) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 311); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); itoa(critterGetStat(obj_dude, STAT_CARRY_WEIGHT), t, 10); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, critterIsOverloaded(obj_dude) ? colorTable[31744] : color); // Melee Damage y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_MELEE_DAMAGE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 304); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); itoa(critterGetStat(obj_dude, STAT_MELEE_DAMAGE), t, 10); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Damage Resistance y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_DAMAGE_RESISTANCE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 305); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); sprintf(t, "%d%%", critterGetStat(obj_dude, STAT_DAMAGE_RESISTANCE)); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Poison Resistance y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_POISON_RESISTANCE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 306); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); sprintf(t, "%d%%", critterGetStat(obj_dude, STAT_POISON_RESISTANCE)); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Radiation Resistance y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_RADIATION_RESISTANCE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 307); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); sprintf(t, "%d%%", critterGetStat(obj_dude, STAT_RADIATION_RESISTANCE)); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Sequence y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_SEQUENCE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 308); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); itoa(critterGetStat(obj_dude, STAT_SEQUENCE), t, 10); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Healing Rate y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_HEALING_RATE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 309); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); itoa(critterGetStat(obj_dude, STAT_HEALING_RATE), t, 10); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); // Critical Chance y += text_height() + 3; if (info_line == EDITOR_FIRST_DERIVED_STAT + EDITOR_DERIVED_STAT_CRITICAL_CHANCE) { color = colorTable[32747]; } else { color = colorTable[992]; } messageListItemText = getmsg(&editor_message_file, &mesg, 310); sprintf(t, "%s", messageListItemText); text_to_buf(win_buf + 640 * y + 194, t, 640, 640, color); sprintf(t, "%d%%", critterGetStat(obj_dude, STAT_CRITICAL_CHANCE)); text_to_buf(win_buf + 640 * y + 288, t, 640, 640, color); } // 0x436154 static void ListSkills(int a1) { int selectedSkill = -1; const char* str; int i; int color; int y; int value; char valueString[32]; if (info_line >= EDITOR_FIRST_SKILL && info_line < 79) { selectedSkill = info_line - EDITOR_FIRST_SKILL; } if (glblmode == 0 && a1 == 0) { win_delete_button(SliderPlusID); win_delete_button(SliderNegID); SliderNegID = -1; SliderPlusID = -1; } buf_to_buf(bckgnd + 370, 270, 252, 640, win_buf + 370, 640); text_font(103); // SKILLS str = getmsg(&editor_message_file, &mesg, 117); text_to_buf(win_buf + 640 * 5 + 380, str, 640, 640, colorTable[18979]); if (!glblmode) { // SKILL POINTS str = getmsg(&editor_message_file, &mesg, 112); text_to_buf(win_buf + 640 * 233 + 400, str, 640, 640, colorTable[18979]); value = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); PrintBigNum(522, 228, 0, value, 0, edit_win); } else { // TAG SKILLS str = getmsg(&editor_message_file, &mesg, 138); text_to_buf(win_buf + 640 * 233 + 422, str, 640, 640, colorTable[18979]); if (a1 == 2 && !first_skill_list) { PrintBigNum(522, 228, ANIMATE, tagskill_count, old_tags, edit_win); } else { PrintBigNum(522, 228, 0, tagskill_count, 0, edit_win); first_skill_list = 0; } } skill_set_tags(temp_tag_skill, NUM_TAGGED_SKILLS); text_font(101); y = 27; for (i = 0; i < SKILL_COUNT; i++) { if (i == selectedSkill) { if (i != temp_tag_skill[0] && i != temp_tag_skill[1] && i != temp_tag_skill[2] && i != temp_tag_skill[3]) { color = colorTable[32747]; } else { color = colorTable[32767]; } } else { if (i != temp_tag_skill[0] && i != temp_tag_skill[1] && i != temp_tag_skill[2] && i != temp_tag_skill[3]) { color = colorTable[992]; } else { color = colorTable[21140]; } } str = skill_name(i); text_to_buf(win_buf + 640 * y + 380, str, 640, 640, color); value = skill_level(obj_dude, i); sprintf(valueString, "%d%%", value); text_to_buf(win_buf + 640 * y + 573, valueString, 640, 640, color); y += text_height() + 1; } if (!glblmode) { y = skill_cursor * (text_height() + 1); slider_y = y + 27; trans_buf_to_buf( grphbmp[EDITOR_GRAPHIC_SLIDER], GInfo[EDITOR_GRAPHIC_SLIDER].width, GInfo[EDITOR_GRAPHIC_SLIDER].height, GInfo[EDITOR_GRAPHIC_SLIDER].width, win_buf + 640 * (y + 16) + 592, 640); if (a1 == 0) { if (SliderPlusID == -1) { SliderPlusID = win_register_button( edit_win, 614, slider_y - 7, GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].width, GInfo[EDITOR_GRAPHIC_SLIDER_PLUS_ON].height, -1, 522, 521, 522, grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_OFF], grphbmp[EDITOR_GRAPHIC_SLIDER_PLUS_ON], NULL, 96); win_register_button_sound_func(SliderPlusID, gsound_red_butt_press, NULL); } if (SliderNegID == -1) { SliderNegID = win_register_button( edit_win, 614, slider_y + 4 - 12 + GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].height, GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_ON].width, GInfo[EDITOR_GRAPHIC_SLIDER_MINUS_OFF].height, -1, 524, 523, 524, grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_OFF], grphbmp[EDITOR_GRAPHIC_SLIDER_MINUS_ON], NULL, 96); win_register_button_sound_func(SliderNegID, gsound_red_butt_press, NULL); } } } } // 0x4365AC static void DrawInfoWin() { int graphicId; char* title; char* description; if (info_line < 0 || info_line >= 98) { return; } buf_to_buf(bckgnd + (640 * 267) + 345, 277, 170, 640, win_buf + (267 * 640) + 345, 640); if (info_line >= 0 && info_line < 7) { description = stat_description(info_line); title = stat_name(info_line); graphicId = stat_picture(info_line); DrawCard(graphicId, title, NULL, description); } else if (info_line >= 7 && info_line < 10) { if (glblmode) { switch (info_line) { case 7: // Character Points description = getmsg(&editor_message_file, &mesg, 121); title = getmsg(&editor_message_file, &mesg, 120); DrawCard(7, title, NULL, description); break; } } else { switch (info_line) { case 7: description = stat_pc_description(PC_STAT_LEVEL); title = stat_pc_name(PC_STAT_LEVEL); DrawCard(7, title, NULL, description); break; case 8: description = stat_pc_description(PC_STAT_EXPERIENCE); title = stat_pc_name(PC_STAT_EXPERIENCE); DrawCard(8, title, NULL, description); break; case 9: // Next Level description = getmsg(&editor_message_file, &mesg, 123); title = getmsg(&editor_message_file, &mesg, 122); DrawCard(9, title, NULL, description); break; } } } else if ((info_line >= 10 && info_line < 43) || (info_line >= 82 && info_line < 98)) { DrawCard(folder_card_fid, folder_card_title, folder_card_title2, folder_card_desc); } else if (info_line >= 43 && info_line < 51) { switch (info_line) { case EDITOR_HIT_POINTS: description = stat_description(STAT_MAXIMUM_HIT_POINTS); title = getmsg(&editor_message_file, &mesg, 300); graphicId = stat_picture(STAT_MAXIMUM_HIT_POINTS); DrawCard(graphicId, title, NULL, description); break; case EDITOR_POISONED: description = getmsg(&editor_message_file, &mesg, 400); title = getmsg(&editor_message_file, &mesg, 312); DrawCard(11, title, NULL, description); break; case EDITOR_RADIATED: description = getmsg(&editor_message_file, &mesg, 401); title = getmsg(&editor_message_file, &mesg, 313); DrawCard(12, title, NULL, description); break; case EDITOR_EYE_DAMAGE: description = getmsg(&editor_message_file, &mesg, 402); title = getmsg(&editor_message_file, &mesg, 314); DrawCard(13, title, NULL, description); break; case EDITOR_CRIPPLED_RIGHT_ARM: description = getmsg(&editor_message_file, &mesg, 403); title = getmsg(&editor_message_file, &mesg, 315); DrawCard(14, title, NULL, description); break; case EDITOR_CRIPPLED_LEFT_ARM: description = getmsg(&editor_message_file, &mesg, 404); title = getmsg(&editor_message_file, &mesg, 316); DrawCard(15, title, NULL, description); break; case EDITOR_CRIPPLED_RIGHT_LEG: description = getmsg(&editor_message_file, &mesg, 405); title = getmsg(&editor_message_file, &mesg, 317); DrawCard(16, title, NULL, description); break; case EDITOR_CRIPPLED_LEFT_LEG: description = getmsg(&editor_message_file, &mesg, 406); title = getmsg(&editor_message_file, &mesg, 318); DrawCard(17, title, NULL, description); break; } } else if (info_line >= EDITOR_FIRST_DERIVED_STAT && info_line < 61) { int derivedStatIndex = info_line - 51; int stat = ndinfoxlt[derivedStatIndex]; description = stat_description(stat); title = stat_name(stat); graphicId = ndrvd[derivedStatIndex]; DrawCard(graphicId, title, NULL, description); } else if (info_line >= EDITOR_FIRST_SKILL && info_line < 79) { int skill = info_line - 61; const char* attributesDescription = skill_attribute(skill); char formatted[150]; // TODO: Size is probably wrong. const char* base = getmsg(&editor_message_file, &mesg, 137); int defaultValue = skill_base(skill); sprintf(formatted, "%s %d%% %s", base, defaultValue, attributesDescription); graphicId = skill_pic(skill); title = skill_name(skill); description = skill_description(skill); DrawCard(graphicId, title, formatted, description); } else if (info_line >= 79 && info_line < 82) { switch (info_line) { case EDITOR_TAG_SKILL: if (glblmode) { // Tag Skill description = getmsg(&editor_message_file, &mesg, 145); title = getmsg(&editor_message_file, &mesg, 144); DrawCard(27, title, NULL, description); } else { // Skill Points description = getmsg(&editor_message_file, &mesg, 131); title = getmsg(&editor_message_file, &mesg, 130); DrawCard(27, title, NULL, description); } break; case EDITOR_SKILLS: // Skills description = getmsg(&editor_message_file, &mesg, 151); title = getmsg(&editor_message_file, &mesg, 150); DrawCard(27, title, NULL, description); break; case EDITOR_OPTIONAL_TRAITS: // Optional Traits description = getmsg(&editor_message_file, &mesg, 147); title = getmsg(&editor_message_file, &mesg, 146); DrawCard(27, title, NULL, description); break; } } } // 0x436C4C static int NameWindow() { char* text; int windowWidth = GInfo[EDITOR_GRAPHIC_CHARWIN].width; int windowHeight = GInfo[EDITOR_GRAPHIC_CHARWIN].height; int nameWindowX = 17; int nameWindowY = 0; int win = win_add(nameWindowX, nameWindowY, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (win == -1) { return -1; } unsigned char* windowBuf = win_get_buf(win); // Copy background memcpy(windowBuf, grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight); trans_buf_to_buf( grphbmp[EDITOR_GRAPHIC_NAME_BOX], GInfo[EDITOR_GRAPHIC_NAME_BOX].width, GInfo[EDITOR_GRAPHIC_NAME_BOX].height, GInfo[EDITOR_GRAPHIC_NAME_BOX].width, windowBuf + windowWidth * 13 + 13, windowWidth); trans_buf_to_buf(grphbmp[EDITOR_GRAPHIC_DONE_BOX], GInfo[EDITOR_GRAPHIC_DONE_BOX].width, GInfo[EDITOR_GRAPHIC_DONE_BOX].height, GInfo[EDITOR_GRAPHIC_DONE_BOX].width, windowBuf + windowWidth * 40 + 13, windowWidth); text_font(103); text = getmsg(&editor_message_file, &mesg, 100); text_to_buf(windowBuf + windowWidth * 44 + 50, text, windowWidth, windowWidth, colorTable[18979]); int doneBtn = win_register_button(win, 26, 44, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 500, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); } win_draw(win); text_font(101); char name[64]; strcpy(name, critter_name(obj_dude)); if (strcmp(name, "None") == 0) { name[0] = '\0'; } // NOTE: I don't understand the nameCopy, not sure what it is used for. It's // definitely there, but I just don' get it. char nameCopy[64]; strcpy(nameCopy, name); if (get_input_str(win, 500, nameCopy, 11, 23, 19, colorTable[992], 100, 0) != -1) { if (nameCopy[0] != '\0') { critter_pc_set_name(nameCopy); PrintBigname(); win_delete(win); return 0; } } // NOTE: original code is a bit different, the following chunk of code written two times. text_font(101); buf_to_buf(grphbmp[EDITOR_GRAPHIC_NAME_BOX], GInfo[EDITOR_GRAPHIC_NAME_BOX].width, GInfo[EDITOR_GRAPHIC_NAME_BOX].height, GInfo[EDITOR_GRAPHIC_NAME_BOX].width, windowBuf + GInfo[EDITOR_GRAPHIC_CHARWIN].width * 13 + 13, GInfo[EDITOR_GRAPHIC_CHARWIN].width); PrintName(windowBuf, GInfo[EDITOR_GRAPHIC_CHARWIN].width); strcpy(nameCopy, name); win_delete(win); return 0; } // 0x436F70 static void PrintName(unsigned char* buf, int pitch) { char str[64]; char* v4; memcpy(str, byte_431D93, 64); text_font(101); v4 = critter_name(obj_dude); // TODO: Check. strcpy(str, v4); text_to_buf(buf + 19 * pitch + 21, str, pitch, pitch, colorTable[992]); } // 0x436FEC static int AgeWindow() { int win; unsigned char* windowBuf; int windowWidth; int windowHeight; const char* messageListItemText; int previousAge; int age; int doneBtn; int prevBtn; int nextBtn; int keyCode; int change; int flags; int savedAge = critterGetStat(obj_dude, STAT_AGE); windowWidth = GInfo[EDITOR_GRAPHIC_CHARWIN].width; windowHeight = GInfo[EDITOR_GRAPHIC_CHARWIN].height; int ageWindowX = GInfo[EDITOR_GRAPHIC_NAME_ON].width + 9; int ageWindowY = 0; win = win_add(ageWindowX, ageWindowY, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (win == -1) { return -1; } windowBuf = win_get_buf(win); memcpy(windowBuf, grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight); trans_buf_to_buf( grphbmp[EDITOR_GRAPHIC_AGE_BOX], GInfo[EDITOR_GRAPHIC_AGE_BOX].width, GInfo[EDITOR_GRAPHIC_AGE_BOX].height, GInfo[EDITOR_GRAPHIC_AGE_BOX].width, windowBuf + windowWidth * 7 + 8, windowWidth); trans_buf_to_buf( grphbmp[EDITOR_GRAPHIC_DONE_BOX], GInfo[EDITOR_GRAPHIC_DONE_BOX].width, GInfo[EDITOR_GRAPHIC_DONE_BOX].height, GInfo[EDITOR_GRAPHIC_DONE_BOX].width, windowBuf + windowWidth * 40 + 13, GInfo[EDITOR_GRAPHIC_CHARWIN].width); text_font(103); messageListItemText = getmsg(&editor_message_file, &mesg, 100); text_to_buf(windowBuf + windowWidth * 44 + 50, messageListItemText, windowWidth, windowWidth, colorTable[18979]); age = critterGetStat(obj_dude, STAT_AGE); PrintBigNum(55, 10, 0, age, 0, win); doneBtn = win_register_button(win, 26, 44, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 500, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); } nextBtn = win_register_button(win, 105, 13, GInfo[EDITOR_GRAPHIC_LEFT_ARROW_DOWN].width, GInfo[EDITOR_GRAPHIC_LEFT_ARROW_DOWN].height, -1, 503, 501, 503, grphbmp[EDITOR_GRAPHIC_RIGHT_ARROW_UP], grphbmp[EDITOR_GRAPHIC_RIGHT_ARROW_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (nextBtn != -1) { win_register_button_sound_func(nextBtn, gsound_med_butt_press, NULL); } prevBtn = win_register_button(win, 19, 13, GInfo[EDITOR_GRAPHIC_RIGHT_ARROW_DOWN].width, GInfo[EDITOR_GRAPHIC_RIGHT_ARROW_DOWN].height, -1, 504, 502, 504, grphbmp[EDITOR_GRAPHIC_LEFT_ARROW_UP], grphbmp[EDITOR_GRAPHIC_LEFT_ARROW_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (prevBtn != -1) { win_register_button_sound_func(prevBtn, gsound_med_butt_press, NULL); } while (true) { _frame_time = get_time(); change = 0; flags = 0; int v32 = 0; keyCode = get_input(); if (keyCode == KEY_RETURN || keyCode == 500) { if (keyCode != 500) { gsound_play_sfx_file("ib1p1xx1"); } win_delete(win); return 0; } else if (keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) { break; } else if (keyCode == 501) { age = critterGetStat(obj_dude, STAT_AGE); if (age < 35) { change = 1; } } else if (keyCode == 502) { age = critterGetStat(obj_dude, STAT_AGE); if (age > 16) { change = -1; } } else if (keyCode == KEY_PLUS || keyCode == KEY_UPPERCASE_N || keyCode == KEY_ARROW_UP) { previousAge = critterGetStat(obj_dude, STAT_AGE); if (previousAge < 35) { flags = ANIMATE; if (inc_stat(obj_dude, STAT_AGE) != 0) { flags = 0; } age = critterGetStat(obj_dude, STAT_AGE); PrintBigNum(55, 10, flags, age, previousAge, win); } } else if (keyCode == KEY_MINUS || keyCode == KEY_UPPERCASE_J || keyCode == KEY_ARROW_DOWN) { previousAge = critterGetStat(obj_dude, STAT_AGE); if (previousAge > 16) { flags = ANIMATE; if (dec_stat(obj_dude, STAT_AGE) != 0) { flags = 0; } age = critterGetStat(obj_dude, STAT_AGE); PrintBigNum(55, 10, flags, age, previousAge, win); } } if (flags == ANIMATE) { PrintAgeBig(); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); win_draw(edit_win); win_draw(win); } if (change != 0) { int v33 = 0; _repFtime = 4; while (true) { _frame_time = get_time(); v33++; if ((!v32 && v33 == 1) || (v32 && v33 > 14.4)) { v32 = true; if (v33 > 14.4) { _repFtime++; if (_repFtime > 24) { _repFtime = 24; } } flags = ANIMATE; previousAge = critterGetStat(obj_dude, STAT_AGE); if (change == 1) { if (previousAge < 35) { if (inc_stat(obj_dude, STAT_AGE) != 0) { flags = 0; } } } else { if (previousAge >= 16) { if (dec_stat(obj_dude, STAT_AGE) != 0) { flags = 0; } } } age = critterGetStat(obj_dude, STAT_AGE); PrintBigNum(55, 10, flags, age, previousAge, win); if (flags == ANIMATE) { PrintAgeBig(); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); win_draw(edit_win); win_draw(win); } } if (v33 > 14.4) { while (elapsed_time(_frame_time) < 1000 / _repFtime) ; } else { while (elapsed_time(_frame_time) < 1000 / 24) ; } keyCode = get_input(); if (keyCode == 503 || keyCode == 504 || game_user_wants_to_quit != 0) { break; } } } else { win_draw(win); while (elapsed_time(_frame_time) < 1000 / 24) ; } } stat_set_base(obj_dude, STAT_AGE, savedAge); PrintAgeBig(); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); win_draw(edit_win); win_draw(win); win_delete(win); return 0; } // 0x437664 static void SexWindow() { char* text; int windowWidth = GInfo[EDITOR_GRAPHIC_CHARWIN].width; int windowHeight = GInfo[EDITOR_GRAPHIC_CHARWIN].height; int genderWindowX = 9 + GInfo[EDITOR_GRAPHIC_NAME_ON].width + GInfo[EDITOR_GRAPHIC_AGE_ON].width; int genderWindowY = 0; int win = win_add(genderWindowX, genderWindowY, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (win == -1) { return; } unsigned char* windowBuf = win_get_buf(win); // Copy background memcpy(windowBuf, grphbmp[EDITOR_GRAPHIC_CHARWIN], windowWidth * windowHeight); trans_buf_to_buf(grphbmp[EDITOR_GRAPHIC_DONE_BOX], GInfo[EDITOR_GRAPHIC_DONE_BOX].width, GInfo[EDITOR_GRAPHIC_DONE_BOX].height, GInfo[EDITOR_GRAPHIC_DONE_BOX].width, windowBuf + windowWidth * 44 + 15, windowWidth); text_font(103); text = getmsg(&editor_message_file, &mesg, 100); text_to_buf(windowBuf + windowWidth * 48 + 52, text, windowWidth, windowWidth, colorTable[18979]); int doneBtn = win_register_button(win, 28, 48, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 500, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); } int btns[2]; btns[0] = win_register_button(win, 22, 2, GInfo[EDITOR_GRAPHIC_MALE_ON].width, GInfo[EDITOR_GRAPHIC_MALE_ON].height, -1, -1, 501, -1, grphbmp[EDITOR_GRAPHIC_MALE_OFF], grphbmp[EDITOR_GRAPHIC_MALE_ON], NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x04 | BUTTON_FLAG_0x02 | BUTTON_FLAG_0x01); if (btns[0] != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, NULL); } btns[1] = win_register_button(win, 71, 3, GInfo[EDITOR_GRAPHIC_FEMALE_ON].width, GInfo[EDITOR_GRAPHIC_FEMALE_ON].height, -1, -1, 502, -1, grphbmp[EDITOR_GRAPHIC_FEMALE_OFF], grphbmp[EDITOR_GRAPHIC_FEMALE_ON], NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x04 | BUTTON_FLAG_0x02 | BUTTON_FLAG_0x01); if (btns[1] != -1) { win_group_radio_buttons(2, btns); win_register_button_sound_func(doneBtn, gsound_red_butt_press, NULL); } int savedGender = critterGetStat(obj_dude, STAT_GENDER); win_set_button_rest_state(btns[savedGender], 1, 0); while (true) { _frame_time = get_time(); int eventCode = get_input(); if (eventCode == KEY_RETURN || eventCode == 500) { if (eventCode == KEY_RETURN) { gsound_play_sfx_file("ib1p1xx1"); } break; } if (eventCode == KEY_ESCAPE || game_user_wants_to_quit != 0) { stat_set_base(obj_dude, STAT_GENDER, savedGender); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); win_draw(edit_win); break; } switch (eventCode) { case KEY_ARROW_LEFT: case KEY_ARROW_RIGHT: if (1) { bool wasMale = win_button_down(btns[0]); win_set_button_rest_state(btns[0], !wasMale, 1); win_set_button_rest_state(btns[1], wasMale, 1); } break; case 501: case 502: // TODO: Original code is slightly different. stat_set_base(obj_dude, STAT_GENDER, eventCode - 501); PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); break; } win_draw(win); while (elapsed_time(_frame_time) < 41) ; } PrintGender(); win_delete(win); } // 0x4379BC static void StatButton(int eventCode) { _repFtime = 4; int savedRemainingCharacterPoints = character_points; if (!glblmode) { return; } int incrementingStat = eventCode - 503; int decrementingStat = eventCode - 510; int v11 = 0; bool cont = true; do { _frame_time = get_time(); if (v11 <= 19.2) { v11++; } if (v11 == 1 || v11 > 19.2) { if (v11 > 19.2) { _repFtime++; if (_repFtime > 24) { _repFtime = 24; } } if (eventCode >= 510) { int previousValue = critterGetStat(obj_dude, decrementingStat); if (dec_stat(obj_dude, decrementingStat) == 0) { character_points++; } else { cont = false; } PrintBasicStat(decrementingStat, cont ? ANIMATE : 0, previousValue); PrintBigNum(126, 282, cont ? ANIMATE : 0, character_points, savedRemainingCharacterPoints, edit_win); stat_recalc_derived(obj_dude); ListDrvdStats(); ListSkills(0); info_line = decrementingStat; } else { int previousValue = stat_get_base(obj_dude, incrementingStat); previousValue += stat_get_bonus(obj_dude, incrementingStat); if (character_points > 0 && previousValue < 10 && inc_stat(obj_dude, incrementingStat) == 0) { character_points--; } else { cont = false; } PrintBasicStat(incrementingStat, cont ? ANIMATE : 0, previousValue); PrintBigNum(126, 282, cont ? ANIMATE : 0, character_points, savedRemainingCharacterPoints, edit_win); stat_recalc_derived(obj_dude); ListDrvdStats(); ListSkills(0); info_line = incrementingStat; } win_draw(edit_win); } if (v11 >= 19.2) { unsigned int delay = 1000 / _repFtime; while (elapsed_time(_frame_time) < delay) { } } else { while (elapsed_time(_frame_time) < 1000 / 24) { } } } while (get_input() != 518 && cont); DrawInfoWin(); } // handle options dialog // // 0x437C08 static int OptionWindow() { int width = GInfo[43].width; int height = GInfo[43].height; // NOTE: The following is a block of general purpose string buffers used in // this function. They are either store path, or strings from .msg files. I // don't know if such usage was intentional in the original code or it's a // result of some kind of compiler optimization. char string1[512]; char string2[512]; char string3[512]; char string4[512]; char string5[512]; // Only two of the these blocks are used as a dialog body. Depending on the // dialog either 1 or 2 strings used from this array. const char* dialogBody[2] = { string5, string2, }; if (glblmode) { int optionsWindowX = 238; int optionsWindowY = 90; int win = win_add(optionsWindowX, optionsWindowY, GInfo[41].width, GInfo[41].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (win == -1) { return -1; } unsigned char* windowBuffer = win_get_buf(win); memcpy(windowBuffer, grphbmp[41], GInfo[41].width * GInfo[41].height); text_font(103); int err = 0; unsigned char* down[5]; unsigned char* up[5]; int size = width * height; int y = 17; int index; for (index = 0; index < 5; index++) { if (err != 0) { break; } do { down[index] = (unsigned char*)mem_malloc(size); if (down[index] == NULL) { err = 1; break; } up[index] = (unsigned char*)mem_malloc(size); if (up[index] == NULL) { err = 2; break; } memcpy(down[index], grphbmp[43], size); memcpy(up[index], grphbmp[42], size); strcpy(string4, getmsg(&editor_message_file, &mesg, 600 + index)); int offset = width * 7 + width / 2 - text_width(string4) / 2; text_to_buf(up[index] + offset, string4, width, width, colorTable[18979]); text_to_buf(down[index] + offset, string4, width, width, colorTable[14723]); int btn = win_register_button(win, 13, y, width, height, -1, -1, -1, 500 + index, up[index], down[index], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_lrg_butt_press, NULL); } } while (0); y += height + 3; } if (err != 0) { if (err == 2) { mem_free(down[index]); } while (--index >= 0) { mem_free(up[index]); mem_free(down[index]); } return -1; } text_font(101); int rc = 0; while (rc == 0) { int keyCode = get_input(); if (game_user_wants_to_quit != 0) { rc = 2; } else if (keyCode == 504) { rc = 2; } else if (keyCode == KEY_RETURN || keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) { // DONE rc = 2; gsound_play_sfx_file("ib1p1xx1"); } else if (keyCode == KEY_ESCAPE) { rc = 2; } else if (keyCode == 503 || keyCode == KEY_UPPERCASE_E || keyCode == KEY_LOWERCASE_E) { // ERASE strcpy(string5, getmsg(&editor_message_file, &mesg, 605)); strcpy(string2, getmsg(&editor_message_file, &mesg, 606)); if (dialog_out(NULL, dialogBody, 2, 169, 126, colorTable[992], NULL, colorTable[992], DIALOG_BOX_YES_NO) != 0) { ResetPlayer(); skill_get_tags(temp_tag_skill, NUM_TAGGED_SKILLS); // NOTE: Uninline. tagskill_count = tagskl_free(); trait_get(&temp_trait[0], &temp_trait[1]); // NOTE: Uninline. trait_count = get_trait_count(); stat_recalc_derived(obj_dude); ResetScreen(); } } else if (keyCode == 502 || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P) { // PRINT TO FILE string4[0] = '\0'; strcat(string4, "*."); strcat(string4, "TXT"); char** fileList; int fileListLength = db_get_file_list(string4, &fileList, 0, 0); if (fileListLength != -1) { // PRINT strcpy(string1, getmsg(&editor_message_file, &mesg, 616)); // PRINT TO FILE strcpy(string4, getmsg(&editor_message_file, &mesg, 602)); if (save_file_dialog(string4, fileList, string1, fileListLength, 168, 80, 0) == 0) { strcat(string1, "."); strcat(string1, "TXT"); string4[0] = '\0'; strcat(string4, string1); if (!db_access(string4)) { // already exists sprintf(string4, "%s %s", strupr(string1), getmsg(&editor_message_file, &mesg, 609)); strcpy(string5, getmsg(&editor_message_file, &mesg, 610)); if (dialog_out(string4, dialogBody, 1, 169, 126, colorTable[32328], NULL, colorTable[32328], 0x10) != 0) { rc = 1; } else { rc = 0; } } else { rc = 1; } if (rc != 0) { string4[0] = '\0'; strcat(string4, string1); if (Save_as_ASCII(string4) == 0) { sprintf(string4, "%s%s", strupr(string1), getmsg(&editor_message_file, &mesg, 607)); dialog_out(string4, NULL, 0, 169, 126, colorTable[992], NULL, colorTable[992], 0); } else { gsound_play_sfx_file("iisxxxx1"); sprintf(string4, "%s%s%s", getmsg(&editor_message_file, &mesg, 611), strupr(string1), "!"); dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[992], 0x01); } } } db_free_file_list(&fileList, 0); } else { gsound_play_sfx_file("iisxxxx1"); strcpy(string4, getmsg(&editor_message_file, &mesg, 615)); dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0); rc = 0; } } else if (keyCode == 501 || keyCode == KEY_UPPERCASE_L || keyCode == KEY_LOWERCASE_L) { // LOAD string4[0] = '\0'; strcat(string4, "*."); strcat(string4, "GCD"); char** fileNameList; int fileNameListLength = db_get_file_list(string4, &fileNameList, 0, 0); if (fileNameListLength != -1) { // NOTE: This value is not copied as in save dialog. char* title = getmsg(&editor_message_file, &mesg, 601); int loadFileDialogRc = file_dialog(title, fileNameList, string3, fileNameListLength, 168, 80, 0); if (loadFileDialogRc == -1) { db_free_file_list(&fileNameList, 0); // FIXME: This branch ignores cleanup at the end of the loop. return -1; } if (loadFileDialogRc == 0) { string4[0] = '\0'; strcat(string4, string3); int oldRemainingCharacterPoints = character_points; ResetPlayer(); if (pc_load_data(string4) == 0) { // NOTE: Uninline. CheckValidPlayer(); skill_get_tags(temp_tag_skill, 4); // NOTE: Uninline. tagskill_count = tagskl_free(); trait_get(&(temp_trait[0]), &(temp_trait[1])); // NOTE: Uninline. trait_count = get_trait_count(); stat_recalc_derived(obj_dude); critter_adjust_hits(obj_dude, 1000); rc = 1; } else { RestorePlayer(); character_points = oldRemainingCharacterPoints; critter_adjust_hits(obj_dude, 1000); gsound_play_sfx_file("iisxxxx1"); strcpy(string4, getmsg(&editor_message_file, &mesg, 612)); strcat(string4, string3); strcat(string4, "!"); dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0); } ResetScreen(); } db_free_file_list(&fileNameList, 0); } else { gsound_play_sfx_file("iisxxxx1"); // Error reading file list! strcpy(string4, getmsg(&editor_message_file, &mesg, 615)); rc = 0; dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0); } } else if (keyCode == 500 || keyCode == KEY_UPPERCASE_S || keyCode == KEY_LOWERCASE_S) { // SAVE string4[0] = '\0'; strcat(string4, "*."); strcat(string4, "GCD"); char** fileNameList; int fileNameListLength = db_get_file_list(string4, &fileNameList, 0, 0); if (fileNameListLength != -1) { strcpy(string1, getmsg(&editor_message_file, &mesg, 617)); strcpy(string4, getmsg(&editor_message_file, &mesg, 600)); if (save_file_dialog(string4, fileNameList, string1, fileNameListLength, 168, 80, 0) == 0) { strcat(string1, "."); strcat(string1, "GCD"); string4[0] = '\0'; strcat(string4, string1); bool shouldSave; if (db_access(string4)) { sprintf(string4, "%s %s", strupr(string1), getmsg(&editor_message_file, &mesg, 609)); strcpy(string5, getmsg(&editor_message_file, &mesg, 610)); if (dialog_out(string4, dialogBody, 1, 169, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO) != 0) { shouldSave = true; } else { shouldSave = false; } } else { shouldSave = true; } if (shouldSave) { skill_set_tags(temp_tag_skill, 4); trait_set(temp_trait[0], temp_trait[1]); string4[0] = '\0'; strcat(string4, string1); if (pc_save_data(string4) != 0) { gsound_play_sfx_file("iisxxxx1"); sprintf(string4, "%s%s!", strupr(string1), getmsg(&editor_message_file, &mesg, 611)); dialog_out(string4, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); rc = 0; } else { sprintf(string4, "%s%s", strupr(string1), getmsg(&editor_message_file, &mesg, 607)); dialog_out(string4, NULL, 0, 169, 126, colorTable[992], NULL, colorTable[992], DIALOG_BOX_LARGE); rc = 1; } } } db_free_file_list(&fileNameList, 0); } else { gsound_play_sfx_file("iisxxxx1"); // Error reading file list! char* msg = getmsg(&editor_message_file, &mesg, 615); dialog_out(msg, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0); rc = 0; } } win_draw(win); } win_delete(win); for (index = 0; index < 5; index++) { mem_free(up[index]); mem_free(down[index]); } return 0; } // Character Editor is not in creation mode - this button is only for // printing character details. char pattern[512]; strcpy(pattern, "*.TXT"); char** fileNames; int filesCount = db_get_file_list(pattern, &fileNames, 0, 0); if (filesCount == -1) { gsound_play_sfx_file("iisxxxx1"); // Error reading file list! strcpy(pattern, getmsg(&editor_message_file, &mesg, 615)); dialog_out(pattern, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 0); return 0; } // PRINT char fileName[512]; strcpy(fileName, getmsg(&editor_message_file, &mesg, 616)); char title[512]; strcpy(title, getmsg(&editor_message_file, &mesg, 602)); if (save_file_dialog(title, fileNames, fileName, filesCount, 168, 80, 0) == 0) { strcat(fileName, ".TXT"); title[0] = '\0'; strcat(title, fileName); int v42 = 0; if (db_access(title)) { sprintf(title, "%s %s", strupr(fileName), getmsg(&editor_message_file, &mesg, 609)); char line2[512]; strcpy(line2, getmsg(&editor_message_file, &mesg, 610)); const char* lines[] = { line2 }; v42 = dialog_out(title, lines, 1, 169, 126, colorTable[32328], NULL, colorTable[32328], 0x10); if (v42) { v42 = 1; } } else { v42 = 1; } if (v42) { title[0] = '\0'; strcpy(title, fileName); if (Save_as_ASCII(title) != 0) { gsound_play_sfx_file("iisxxxx1"); sprintf(title, "%s%s%s", getmsg(&editor_message_file, &mesg, 611), strupr(fileName), "!"); dialog_out(title, NULL, 0, 169, 126, colorTable[32328], NULL, colorTable[32328], 1); } } } db_free_file_list(&fileNames, 0); return 0; } // 0x4390B4 bool db_access(const char* fname) { File* stream = db_fopen(fname, "rb"); if (stream == NULL) { return false; } db_fclose(stream); return true; } // 0x4390D0 static int Save_as_ASCII(const char* fileName) { File* stream = db_fopen(fileName, "wt"); if (stream == NULL) { return -1; } db_fputs("\n", stream); db_fputs("\n", stream); char title1[256]; char title2[256]; char title3[256]; char padding[256]; // FALLOUT strcpy(title1, getmsg(&editor_message_file, &mesg, 620)); // NOTE: Uninline. padding[0] = '\0'; AddSpaces(padding, (80 - strlen(title1)) / 2 - 2); strcat(padding, title1); strcat(padding, "\n"); db_fputs(padding, stream); // VAULT-13 PERSONNEL RECORD strcpy(title1, getmsg(&editor_message_file, &mesg, 621)); // NOTE: Uninline. padding[0] = '\0'; AddSpaces(padding, (80 - strlen(title1)) / 2 - 2); strcat(padding, title1); strcat(padding, "\n"); db_fputs(padding, stream); int month; int day; int year; game_time_date(&month, &day, &year); sprintf(title1, "%.2d %s %d %.4d %s", day, getmsg(&editor_message_file, &mesg, 500 + month - 1), year, game_time_hour(), getmsg(&editor_message_file, &mesg, 622)); // NOTE: Uninline. padding[0] = '\0'; AddSpaces(padding, (80 - strlen(title1)) / 2 - 2); strcat(padding, title1); strcat(padding, "\n"); db_fputs(padding, stream); // Blank line db_fputs("\n", stream); // Name sprintf(title1, "%s %s", getmsg(&editor_message_file, &mesg, 642), critter_name(obj_dude)); int paddingLength = 27 - strlen(title1); if (paddingLength > 0) { // NOTE: Uninline. padding[0] = '\0'; AddSpaces(padding, paddingLength); strcat(title1, padding); } // Age sprintf(title2, "%s%s %d", title1, getmsg(&editor_message_file, &mesg, 643), critterGetStat(obj_dude, STAT_AGE)); // Gender sprintf(title3, "%s%s %s", title2, getmsg(&editor_message_file, &mesg, 644), getmsg(&editor_message_file, &mesg, 645 + critterGetStat(obj_dude, STAT_GENDER))); db_fputs(title3, stream); db_fputs("\n", stream); sprintf(title1, "%s %.2d %s %s ", getmsg(&editor_message_file, &mesg, 647), stat_pc_get(PC_STAT_LEVEL), getmsg(&editor_message_file, &mesg, 648), itostndn(stat_pc_get(PC_STAT_EXPERIENCE), title3)); paddingLength = 12 - strlen(title3); if (paddingLength > 0) { // NOTE: Uninline. padding[0] = '\0'; AddSpaces(padding, paddingLength); strcat(title1, padding); } sprintf(title2, "%s%s %s", title1, getmsg(&editor_message_file, &mesg, 649), itostndn(stat_pc_min_exp(), title3)); db_fputs(title2, stream); db_fputs("\n", stream); db_fputs("\n", stream); // Statistics sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 623)); // Strength / Hit Points / Sequence // // FIXME: There is bug - it shows strength instead of sequence. sprintf(title1, "%s %.2d %s %.3d/%.3d %s %.2d", getmsg(&editor_message_file, &mesg, 624), critterGetStat(obj_dude, STAT_STRENGTH), getmsg(&editor_message_file, &mesg, 625), critter_get_hits(obj_dude), critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS), getmsg(&editor_message_file, &mesg, 626), critterGetStat(obj_dude, STAT_STRENGTH)); db_fputs(title1, stream); db_fputs("\n", stream); // Perception / Armor Class / Healing Rate sprintf(title1, "%s %.2d %s %.3d %s %.2d", getmsg(&editor_message_file, &mesg, 627), critterGetStat(obj_dude, STAT_PERCEPTION), getmsg(&editor_message_file, &mesg, 628), critterGetStat(obj_dude, STAT_ARMOR_CLASS), getmsg(&editor_message_file, &mesg, 629), critterGetStat(obj_dude, STAT_HEALING_RATE)); db_fputs(title1, stream); db_fputs("\n", stream); // Endurance / Action Points / Critical Chance sprintf(title1, "%s %.2d %s %.2d %s %.3d%%", getmsg(&editor_message_file, &mesg, 630), critterGetStat(obj_dude, STAT_ENDURANCE), getmsg(&editor_message_file, &mesg, 631), critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS), getmsg(&editor_message_file, &mesg, 632), critterGetStat(obj_dude, STAT_CRITICAL_CHANCE)); db_fputs(title1, stream); db_fputs("\n", stream); // Charisma / Melee Damage / Carry Weight sprintf(title1, "%s %.2d %s %.2d %s %.3d lbs.", getmsg(&editor_message_file, &mesg, 633), critterGetStat(obj_dude, STAT_CHARISMA), getmsg(&editor_message_file, &mesg, 634), critterGetStat(obj_dude, STAT_MELEE_DAMAGE), getmsg(&editor_message_file, &mesg, 635), critterGetStat(obj_dude, STAT_CARRY_WEIGHT)); db_fputs(title1, stream); db_fputs("\n", stream); // Intelligence / Damage Resistance sprintf(title1, "%s %.2d %s %.3d%%", getmsg(&editor_message_file, &mesg, 636), critterGetStat(obj_dude, STAT_INTELLIGENCE), getmsg(&editor_message_file, &mesg, 637), critterGetStat(obj_dude, STAT_DAMAGE_RESISTANCE)); db_fputs(title1, stream); db_fputs("\n", stream); // Agility / Radiation Resistance sprintf(title1, "%s %.2d %s %.3d%%", getmsg(&editor_message_file, &mesg, 638), critterGetStat(obj_dude, STAT_AGILITY), getmsg(&editor_message_file, &mesg, 639), critterGetStat(obj_dude, STAT_RADIATION_RESISTANCE)); db_fputs(title1, stream); db_fputs("\n", stream); // Luck / Poison Resistance sprintf(title1, "%s %.2d %s %.3d%%", getmsg(&editor_message_file, &mesg, 640), critterGetStat(obj_dude, STAT_LUCK), getmsg(&editor_message_file, &mesg, 641), critterGetStat(obj_dude, STAT_POISON_RESISTANCE)); db_fputs(title1, stream); db_fputs("\n", stream); db_fputs("\n", stream); db_fputs("\n", stream); if (temp_trait[0] != -1) { // ::: Traits ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 650)); db_fputs(title1, stream); // NOTE: The original code does not use loop, or it was optimized away. for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) { if (temp_trait[index] != -1) { sprintf(title1, " %s", trait_name(temp_trait[index])); db_fputs(title1, stream); db_fputs("\n", stream); } } } int perk = 0; for (; perk < PERK_COUNT; perk++) { if (perk_level(obj_dude, perk) != 0) { break; } } if (perk < PERK_COUNT) { // ::: Perks ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 651)); db_fputs(title1, stream); for (perk = 0; perk < PERK_COUNT; perk++) { int rank = perk_level(obj_dude, perk); if (rank != 0) { if (rank == 1) { sprintf(title1, " %s", perk_name(perk)); } else { sprintf(title1, " %s (%d)", perk_name(perk), rank); } db_fputs(title1, stream); db_fputs("\n", stream); } } } db_fputs("\n", stream); // ::: Karma ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 652)); db_fputs(title1, stream); for (int index = 0; index < karma_vars_count; index++) { KarmaEntry* karmaEntry = &(karma_vars[index]); if (karmaEntry->gvar == GVAR_PLAYER_REPUTATION) { int reputation = 0; for (; reputation < general_reps_count; reputation++) { GenericReputationEntry* reputationDescription = &(general_reps[reputation]); if (game_global_vars[GVAR_PLAYER_REPUTATION] >= reputationDescription->threshold) { break; } } if (reputation < general_reps_count) { GenericReputationEntry* reputationDescription = &(general_reps[reputation]); sprintf(title1, " %s: %s (%s)", getmsg(&editor_message_file, &mesg, 125), itoa(game_global_vars[GVAR_PLAYER_REPUTATION], title2, 10), getmsg(&editor_message_file, &mesg, reputationDescription->name)); db_fputs(title1, stream); db_fputs("\n", stream); } } else { if (game_global_vars[karmaEntry->gvar] != 0) { sprintf(title1, " %s", getmsg(&editor_message_file, &mesg, karmaEntry->name)); db_fputs(title1, stream); db_fputs("\n", stream); } } } bool hasTownReputationHeading = false; for (int index = 0; index < TOWN_REPUTATION_COUNT; index++) { const TownReputationEntry* pair = &(town_rep_info[index]); if (wmAreaIsKnown(pair->city)) { if (!hasTownReputationHeading) { db_fputs("\n", stream); // ::: Reputation ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 657)); db_fputs(title1, stream); hasTownReputationHeading = true; } wmGetAreaIdxName(pair->city, title2); int townReputation = game_global_vars[pair->gvar]; int townReputationMessageId; if (townReputation < -30) { townReputationMessageId = 2006; // Vilified } else if (townReputation < -15) { townReputationMessageId = 2005; // Hated } else if (townReputation < 0) { townReputationMessageId = 2004; // Antipathy } else if (townReputation == 0) { townReputationMessageId = 2003; // Neutral } else if (townReputation < 15) { townReputationMessageId = 2002; // Accepted } else if (townReputation < 30) { townReputationMessageId = 2001; // Liked } else { townReputationMessageId = 2000; // Idolized } sprintf(title1, " %s: %s", title2, getmsg(&editor_message_file, &mesg, townReputationMessageId)); db_fputs(title1, stream); db_fputs("\n", stream); } } bool hasAddictionsHeading = false; for (int index = 0; index < ADDICTION_REPUTATION_COUNT; index++) { if (game_global_vars[addiction_vars[index]] != 0) { if (!hasAddictionsHeading) { db_fputs("\n", stream); // ::: Addictions ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 656)); db_fputs(title1, stream); hasAddictionsHeading = true; } sprintf(title1, " %s", getmsg(&editor_message_file, &mesg, 1004 + index)); db_fputs(title1, stream); db_fputs("\n", stream); } } db_fputs("\n", stream); // ::: Skills ::: / ::: Kills ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 653)); db_fputs(title1, stream); int killType = 0; for (int skill = 0; skill < SKILL_COUNT; skill++) { sprintf(title1, "%s ", skill_name(skill)); // NOTE: Uninline. AddDots(title1 + strlen(title1), 16 - strlen(title1)); bool hasKillType = false; for (; killType < KILL_TYPE_COUNT; killType++) { int killsCount = critter_kill_count(killType); if (killsCount > 0) { sprintf(title2, "%s ", critter_kill_name(killType)); // NOTE: Uninline. AddDots(title2 + strlen(title2), 16 - strlen(title2)); sprintf(title3, " %s %.3d%% %s %.3d\n", title1, skill_level(obj_dude, skill), title2, killsCount); hasKillType = true; break; } } if (!hasKillType) { sprintf(title3, " %s %.3d%%\n", title1, skill_level(obj_dude, skill)); } } db_fputs("\n", stream); db_fputs("\n", stream); // ::: Inventory ::: sprintf(title1, "%s\n", getmsg(&editor_message_file, &mesg, 654)); db_fputs(title1, stream); Inventory* inventory = &(obj_dude->data.inventory); for (int index = 0; index < inventory->length; index += 3) { title1[0] = '\0'; for (int column = 0; column < 3; column++) { int inventoryItemIndex = index + column; if (inventoryItemIndex >= inventory->length) { break; } InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]); sprintf(title2, " %sx %s", itostndn(inventoryItem->quantity, title3), object_name(inventoryItem->item)); int length = 25 - strlen(title2); if (length < 0) { length = 0; } AddSpaces(title2, length); strcat(title1, title2); } strcat(title1, "\n"); db_fputs(title1, stream); } db_fputs("\n", stream); // Total Weight: sprintf(title1, "%s %d lbs.", getmsg(&editor_message_file, &mesg, 655), item_total_weight(obj_dude)); db_fputs(title1, stream); db_fputs("\n", stream); db_fputs("\n", stream); db_fputs("\n", stream); db_fclose(stream); return 0; } // 0x43A55C char* AddSpaces(char* string, int length) { char* pch = string + strlen(string); for (int index = 0; index < length; index++) { *pch++ = ' '; } *pch = '\0'; return string; } // NOTE: Inlined. // // 0x43A58C static char* AddDots(char* string, int length) { char* pch = string + strlen(string); for (int index = 0; index < length; index++) { *pch++ = '.'; } *pch = '\0'; return string; } // 0x43A4BC static void ResetScreen() { info_line = 0; skill_cursor = 0; slider_y = 27; folder = 0; if (glblmode) { PrintBigNum(126, 282, 0, character_points, 0, edit_win); } else { DrawFolder(); PrintLevelWin(); } PrintBigname(); PrintAgeBig(); PrintGender(); ListTraits(); ListSkills(0); PrintBasicStat(7, 0, 0); ListDrvdStats(); DrawInfoWin(); win_draw(edit_win); } // 0x43A5BC static void RegInfoAreas() { win_register_button(edit_win, 19, 38, 125, 227, -1, -1, 525, -1, NULL, NULL, NULL, 0); win_register_button(edit_win, 28, 280, 124, 32, -1, -1, 526, -1, NULL, NULL, NULL, 0); if (glblmode) { win_register_button(edit_win, 52, 324, 169, 20, -1, -1, 533, -1, NULL, NULL, NULL, 0); win_register_button(edit_win, 47, 353, 245, 100, -1, -1, 534, -1, NULL, NULL, NULL, 0); } else { win_register_button(edit_win, 28, 363, 283, 105, -1, -1, 527, -1, NULL, NULL, NULL, 0); } win_register_button(edit_win, 191, 41, 122, 110, -1, -1, 528, -1, NULL, NULL, NULL, 0); win_register_button(edit_win, 191, 175, 122, 135, -1, -1, 529, -1, NULL, NULL, NULL, 0); win_register_button(edit_win, 376, 5, 223, 20, -1, -1, 530, -1, NULL, NULL, NULL, 0); win_register_button(edit_win, 370, 27, 223, 195, -1, -1, 531, -1, NULL, NULL, NULL, 0); win_register_button(edit_win, 396, 228, 171, 25, -1, -1, 532, -1, NULL, NULL, NULL, 0); } // NOTE: Inlined. // // 0x43A79C static int CheckValidPlayer() { int stat; stat_recalc_derived(obj_dude); stat_pc_set_defaults(); for (stat = 0; stat < SAVEABLE_STAT_COUNT; stat++) { stat_set_bonus(obj_dude, stat, 0); } perk_reset(); stat_recalc_derived(obj_dude); return 1; } // copy character to editor // // 0x43A7DC static void SavePlayer() { Proto* proto; proto_ptr(obj_dude->pid, &proto); critter_copy(&dude_data, &(proto->critter.data)); hp_back = critter_get_hits(obj_dude); strncpy(name_save, critter_name(obj_dude), 32); last_level_back = last_level; // NOTE: Uninline. push_perks(); free_perk_back = free_perk; upsent_points_back = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); skill_get_tags(tag_skill_back, NUM_TAGGED_SKILLS); trait_get(&(trait_back[0]), &(trait_back[1])); for (int skill = 0; skill < SKILL_COUNT; skill++) { skillsav[skill] = skill_level(obj_dude, skill); } } // copy editor to character // // 0x43A8BC static void RestorePlayer() { Proto* proto; int cur_hp; pop_perks(); proto_ptr(obj_dude->pid, &proto); critter_copy(&(proto->critter.data), &dude_data); critter_pc_set_name(name_save); last_level = last_level_back; free_perk = free_perk_back; stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, upsent_points_back); skill_set_tags(tag_skill_back, NUM_TAGGED_SKILLS); trait_set(trait_back[0], trait_back[1]); skill_get_tags(temp_tag_skill, NUM_TAGGED_SKILLS); // NOTE: Uninline. tagskill_count = tagskl_free(); trait_get(&(temp_trait[0]), &(temp_trait[1])); // NOTE: Uninline. trait_count = get_trait_count(); stat_recalc_derived(obj_dude); cur_hp = critter_get_hits(obj_dude); critter_adjust_hits(obj_dude, hp_back - cur_hp); } // 0x43A9CC char* itostndn(int value, char* dest) { // 0x431DD4 static const int v16[7] = { 1000000, 100000, 10000, 1000, 100, 10, 1, }; char* savedDest = dest; if (value != 0) { *dest = '\0'; bool v3 = false; for (int index = 0; index < 7; index++) { int v18 = value / v16[index]; if (v18 > 0 || v3) { char temp[64]; // TODO: Size is probably wrong. itoa(v18, temp, 10); strcat(dest, temp); v3 = true; value -= v16[index] * v18; if (index == 0 || index == 3) { strcat(dest, ","); } } } } else { strcpy(dest, "0"); } return savedDest; } // 0x43AAEC static int DrawCard(int graphicId, const char* name, const char* attributes, char* description) { CacheEntry* graphicHandle; Size size; int fid; unsigned char* buf; unsigned char* ptr; int v9; int x; int y; short beginnings[WORD_WRAP_MAX_COUNT]; short beginningsCount; fid = art_id(OBJ_TYPE_SKILLDEX, graphicId, 0, 0, 0); buf = art_lock(fid, &graphicHandle, &(size.width), &(size.height)); if (buf == NULL) { return -1; } buf_to_buf(buf, size.width, size.height, size.width, win_buf + 640 * 309 + 484, 640); v9 = 150; ptr = buf; for (y = 0; y < size.height; y++) { for (x = 0; x < size.width; x++) { if (HighRGB(*ptr) < 2 && v9 >= x) { v9 = x; } ptr++; } } v9 -= 8; if (v9 < 0) { v9 = 0; } text_font(102); text_to_buf(win_buf + 640 * 272 + 348, name, 640, 640, colorTable[0]); int nameFontLineHeight = text_height(); if (attributes != NULL) { int nameWidth = text_width(name); text_font(101); int attributesFontLineHeight = text_height(); text_to_buf(win_buf + 640 * (268 + nameFontLineHeight - attributesFontLineHeight) + 348 + nameWidth + 8, attributes, 640, 640, colorTable[0]); } y = nameFontLineHeight; win_line(edit_win, 348, y + 272, 613, y + 272, colorTable[0]); win_line(edit_win, 348, y + 273, 613, y + 273, colorTable[0]); text_font(101); int descriptionFontLineHeight = text_height(); if (word_wrap(description, v9 + 136, beginnings, &beginningsCount) != 0) { // TODO: Leaking graphic handle. return -1; } y = 315; for (short i = 0; i < beginningsCount - 1; i++) { short beginning = beginnings[i]; short ending = beginnings[i + 1]; char c = description[ending]; description[ending] = '\0'; text_to_buf(win_buf + 640 * y + 348, description + beginning, 640, 640, colorTable[0]); description[ending] = c; y += descriptionFontLineHeight; } if (graphicId != old_fid1 || strcmp(name, old_str1) != 0) { if (frstc_draw1) { gsound_play_sfx_file("isdxxxx1"); } } strcpy(old_str1, name); old_fid1 = graphicId; frstc_draw1 = true; art_ptr_unlock(graphicHandle); return 0; } // 0x43AE84 static void FldrButton() { mouse_get_position(&mouse_xpos, &mouse_ypos); gsound_play_sfx_file("ib3p1xx1"); if (mouse_xpos >= 208) { info_line = 41; folder = EDITOR_FOLDER_KILLS; } else if (mouse_xpos > 110) { info_line = 42; folder = EDITOR_FOLDER_KARMA; } else { info_line = 40; folder = EDITOR_FOLDER_PERKS; } DrawFolder(); DrawInfoWin(); } // 0x43AF40 static void InfoButton(int eventCode) { mouse_get_position(&mouse_xpos, &mouse_ypos); switch (eventCode) { case 525: if (1) { // TODO: Original code is slightly different. double mouseY = mouse_ypos; for (int index = 0; index < 7; index++) { double buttonTop = StatYpos[index]; double buttonBottom = StatYpos[index] + 22; double allowance = 5.0 - index * 0.25; if (mouseY >= buttonTop - allowance && mouseY <= buttonBottom + allowance) { info_line = index; break; } } } break; case 526: if (glblmode) { info_line = 7; } else { int offset = mouse_ypos - 280; if (offset < 0) { offset = 0; } info_line = offset / 10 + 7; } break; case 527: if (!glblmode) { text_font(101); int offset = mouse_ypos - 364; if (offset < 0) { offset = 0; } info_line = offset / (text_height() + 1) + 10; } break; case 528: if (1) { int offset = mouse_ypos - 41; if (offset < 0) { offset = 0; } info_line = offset / 13 + 43; } break; case 529: { int offset = mouse_ypos - 175; if (offset < 0) { offset = 0; } info_line = offset / 13 + 51; break; } case 530: info_line = 80; break; case 531: if (1) { int offset = mouse_ypos - 27; if (offset < 0) { offset = 0; } skill_cursor = (int)(offset * 0.092307694); if (skill_cursor >= 18) { skill_cursor = 17; } info_line = skill_cursor + 61; } break; case 532: info_line = 79; break; case 533: info_line = 81; break; case 534: if (1) { text_font(101); // TODO: Original code is slightly different. double mouseY = mouse_ypos; double fontLineHeight = text_height(); double y = 353.0; double step = text_height() + 3 + 0.56; int index; for (index = 0; index < 8; index++) { if (mouseY >= y - 4.0 && mouseY <= y + fontLineHeight) { break; } y += step; } if (index == 8) { index = 7; } info_line = index + 82; if (mouse_xpos >= 169) { info_line += 8; } } break; } PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListTraits(); ListSkills(0); PrintLevelWin(); DrawFolder(); ListDrvdStats(); DrawInfoWin(); } // 0x43B230 static void SliderBtn(int keyCode) { if (glblmode) { return; } int unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); _repFtime = 4; bool isUsingKeyboard = false; int rc = 0; switch (keyCode) { case KEY_PLUS: case KEY_UPPERCASE_N: case KEY_ARROW_RIGHT: isUsingKeyboard = true; keyCode = 521; break; case KEY_MINUS: case KEY_UPPERCASE_J: case KEY_ARROW_LEFT: isUsingKeyboard = true; keyCode = 523; break; } char title[64]; char body1[64]; char body2[64]; const char* body[] = { body1, body2, }; int repeatDelay = 0; for (;;) { _frame_time = get_time(); if (repeatDelay <= 19.2) { repeatDelay++; } if (repeatDelay == 1 || repeatDelay > 19.2) { if (repeatDelay > 19.2) { _repFtime++; if (_repFtime > 24) { _repFtime = 24; } } rc = 1; if (keyCode == 521) { if (stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS) > 0) { if (skill_inc_point(obj_dude, skill_cursor) == -3) { gsound_play_sfx_file("iisxxxx1"); sprintf(title, "%s:", skill_name(skill_cursor)); // At maximum level. strcpy(body1, getmsg(&editor_message_file, &mesg, 132)); // Unable to increment it. strcpy(body2, getmsg(&editor_message_file, &mesg, 133)); dialog_out(title, body, 2, 192, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); rc = -1; } } else { gsound_play_sfx_file("iisxxxx1"); // Not enough skill points available. strcpy(title, getmsg(&editor_message_file, &mesg, 136)); dialog_out(title, NULL, 0, 192, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); rc = -1; } } else if (keyCode == 523) { if (skill_level(obj_dude, skill_cursor) <= skillsav[skill_cursor]) { rc = 0; } else { if (skill_dec_point(obj_dude, skill_cursor) == -2) { rc = 0; } } if (rc == 0) { gsound_play_sfx_file("iisxxxx1"); sprintf(title, "%s:", skill_name(skill_cursor)); // At minimum level. strcpy(body1, getmsg(&editor_message_file, &mesg, 134)); // Unable to decrement it. strcpy(body2, getmsg(&editor_message_file, &mesg, 135)); dialog_out(title, body, 2, 192, 126, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); rc = -1; } } info_line = skill_cursor + 61; DrawInfoWin(); ListSkills(1); int flags; if (rc == 1) { flags = ANIMATE; } else { flags = 0; } PrintBigNum(522, 228, flags, stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS), unspentSp, edit_win); win_draw(edit_win); } if (!isUsingKeyboard) { unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); if (repeatDelay >= 19.2) { while (elapsed_time(_frame_time) < 1000 / _repFtime) { } } else { while (elapsed_time(_frame_time) < 1000 / 24) { } } int keyCode = get_input(); if (keyCode != 522 && keyCode != 524 && rc != -1) { continue; } } return; } } // 0x43B64C static int tagskl_free() { int taggedSkillCount; int index; taggedSkillCount = 0; for (index = 3; index >= 0; index--) { if (temp_tag_skill[index] != -1) { break; } taggedSkillCount++; } if (glblmode == 1) { taggedSkillCount--; } return taggedSkillCount; } // 0x43B67C static void TagSkillSelect(int skill) { int insertionIndex; // NOTE: Uninline. old_tags = tagskl_free(); if (skill == temp_tag_skill[0] || skill == temp_tag_skill[1] || skill == temp_tag_skill[2] || skill == temp_tag_skill[3]) { if (skill == temp_tag_skill[0]) { temp_tag_skill[0] = temp_tag_skill[1]; temp_tag_skill[1] = temp_tag_skill[2]; temp_tag_skill[2] = -1; } else if (skill == temp_tag_skill[1]) { temp_tag_skill[1] = temp_tag_skill[2]; temp_tag_skill[2] = -1; } else { temp_tag_skill[2] = -1; } } else { if (tagskill_count > 0) { insertionIndex = 0; for (int index = 0; index < 3; index++) { if (temp_tag_skill[index] == -1) { break; } insertionIndex++; } temp_tag_skill[insertionIndex] = skill; } else { gsound_play_sfx_file("iisxxxx1"); char line1[128]; strcpy(line1, getmsg(&editor_message_file, &mesg, 140)); char line2[128]; strcpy(line2, getmsg(&editor_message_file, &mesg, 141)); const char* lines[] = { line2 }; dialog_out(line1, lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0); } } // NOTE: Uninline. tagskill_count = tagskl_free(); info_line = skill + 61; PrintBasicStat(RENDER_ALL_STATS, 0, 0); ListDrvdStats(); ListSkills(2); DrawInfoWin(); win_draw(edit_win); } // 0x43B8A8 static void ListTraits() { int v0 = -1; int i; int color; const char* traitName; double step; double y; if (glblmode != 1) { return; } if (info_line >= 82 && info_line < 98) { v0 = info_line - 82; } buf_to_buf(bckgnd + 640 * 353 + 47, 245, 100, 640, win_buf + 640 * 353 + 47, 640); text_font(101); trait_set(temp_trait[0], temp_trait[1]); step = text_height() + 3 + 0.56; y = 353; for (i = 0; i < 8; i++) { if (i == v0) { if (i != temp_trait[0] && i != temp_trait[1]) { color = colorTable[32747]; } else { color = colorTable[32767]; } folder_card_fid = trait_pic(i); folder_card_title = trait_name(i); folder_card_title2 = NULL; folder_card_desc = trait_description(i); } else { if (i != temp_trait[0] && i != temp_trait[1]) { color = colorTable[992]; } else { color = colorTable[21140]; } } traitName = trait_name(i); text_to_buf(win_buf + 640 * (int)y + 47, traitName, 640, 640, color); y += step; } y = 353; for (i = 8; i < 16; i++) { if (i == v0) { if (i != temp_trait[0] && i != temp_trait[1]) { color = colorTable[32747]; } else { color = colorTable[32767]; } folder_card_fid = trait_pic(i); folder_card_title = trait_name(i); folder_card_title2 = NULL; folder_card_desc = trait_description(i); } else { if (i != temp_trait[0] && i != temp_trait[1]) { color = colorTable[992]; } else { color = colorTable[21140]; } } traitName = trait_name(i); text_to_buf(win_buf + 640 * (int)y + 199, traitName, 640, 640, color); y += step; } } // NOTE: Inlined. // // 0x43BAE8 static int get_trait_count() { int traitCount; int index; traitCount = 0; for (index = 1; index >= 0; index--) { if (temp_trait[index] != -1) { break; } traitCount++; } return traitCount; } // 0x43BB0C static void TraitSelect(int trait) { if (trait == temp_trait[0] || trait == temp_trait[1]) { if (trait == temp_trait[0]) { temp_trait[0] = temp_trait[1]; temp_trait[1] = -1; } else { temp_trait[1] = -1; } } else { if (trait_count == 0) { gsound_play_sfx_file("iisxxxx1"); char line1[128]; strcpy(line1, getmsg(&editor_message_file, &mesg, 148)); char line2[128]; strcpy(line2, getmsg(&editor_message_file, &mesg, 149)); const char* lines = { line2 }; dialog_out(line1, &lines, 1, 192, 126, colorTable[32328], 0, colorTable[32328], 0); } else { for (int index = 0; index < 2; index++) { if (temp_trait[index] == -1) { temp_trait[index] = trait; break; } } } } // NOTE: Uninline. trait_count = get_trait_count(); info_line = trait + EDITOR_FIRST_TRAIT; ListTraits(); ListSkills(0); stat_recalc_derived(obj_dude); PrintBigNum(126, 282, 0, character_points, 0, edit_win); PrintBasicStat(RENDER_ALL_STATS, false, 0); ListDrvdStats(); DrawInfoWin(); win_draw(edit_win); } // 0x43BCE0 static void list_karma() { char* msg; char formattedText[256]; folder_clear(); bool hasSelection = false; for (int index = 0; index < karma_vars_count; index++) { KarmaEntry* karmaDescription = &(karma_vars[index]); if (karmaDescription->gvar == GVAR_PLAYER_REPUTATION) { int reputation; for (reputation = 0; reputation < general_reps_count; reputation++) { GenericReputationEntry* reputationDescription = &(general_reps[reputation]); if (game_global_vars[GVAR_PLAYER_REPUTATION] >= reputationDescription->threshold) { break; } } if (reputation != general_reps_count) { GenericReputationEntry* reputationDescription = &(general_reps[reputation]); char reputationValue[32]; itoa(game_global_vars[GVAR_PLAYER_REPUTATION], reputationValue, 10); sprintf(formattedText, "%s: %s (%s)", getmsg(&editor_message_file, &mesg, 125), reputationValue, getmsg(&editor_message_file, &mesg, reputationDescription->name)); if (folder_print_line(formattedText)) { folder_card_fid = karmaDescription->art_num; folder_card_title = getmsg(&editor_message_file, &mesg, 125); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, karmaDescription->description); hasSelection = true; } } } else { if (game_global_vars[karmaDescription->gvar] != 0) { msg = getmsg(&editor_message_file, &mesg, karmaDescription->name); if (folder_print_line(msg)) { folder_card_fid = karmaDescription->art_num; folder_card_title = getmsg(&editor_message_file, &mesg, karmaDescription->name); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, karmaDescription->description); hasSelection = true; } } } } bool hasTownReputationHeading = false; for (int index = 0; index < TOWN_REPUTATION_COUNT; index++) { const TownReputationEntry* pair = &(town_rep_info[index]); if (wmAreaIsKnown(pair->city)) { if (!hasTownReputationHeading) { msg = getmsg(&editor_message_file, &mesg, 4000); if (folder_print_seperator(msg)) { folder_card_fid = 48; folder_card_title = getmsg(&editor_message_file, &mesg, 4000); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, 4100); } hasTownReputationHeading = true; } char cityShortName[40]; wmGetAreaIdxName(pair->city, cityShortName); int townReputation = game_global_vars[pair->gvar]; int townReputationGraphicId; int townReputationBaseMessageId; if (townReputation < -30) { townReputationGraphicId = 150; townReputationBaseMessageId = 2006; // Vilified } else if (townReputation < -15) { townReputationGraphicId = 153; townReputationBaseMessageId = 2005; // Hated } else if (townReputation < 0) { townReputationGraphicId = 153; townReputationBaseMessageId = 2004; // Antipathy } else if (townReputation == 0) { townReputationGraphicId = 141; townReputationBaseMessageId = 2003; // Neutral } else if (townReputation < 15) { townReputationGraphicId = 137; townReputationBaseMessageId = 2002; // Accepted } else if (townReputation < 30) { townReputationGraphicId = 137; townReputationBaseMessageId = 2001; // Liked } else { townReputationGraphicId = 135; townReputationBaseMessageId = 2000; // Idolized } msg = getmsg(&editor_message_file, &mesg, townReputationBaseMessageId); sprintf(formattedText, "%s: %s", cityShortName, msg); if (folder_print_line(formattedText)) { folder_card_fid = townReputationGraphicId; folder_card_title = getmsg(&editor_message_file, &mesg, townReputationBaseMessageId); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, townReputationBaseMessageId + 100); hasSelection = 1; } } } bool hasAddictionsHeading = false; for (int index = 0; index < ADDICTION_REPUTATION_COUNT; index++) { if (game_global_vars[addiction_vars[index]] != 0) { if (!hasAddictionsHeading) { // Addictions msg = getmsg(&editor_message_file, &mesg, 4001); if (folder_print_seperator(msg)) { folder_card_fid = 53; folder_card_title = getmsg(&editor_message_file, &mesg, 4001); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, 4101); hasSelection = 1; } hasAddictionsHeading = true; } msg = getmsg(&editor_message_file, &mesg, 1004 + index); if (folder_print_line(msg)) { folder_card_fid = addiction_pics[index]; folder_card_title = getmsg(&editor_message_file, &mesg, 1004 + index); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, 1104 + index); hasSelection = 1; } } } if (!hasSelection) { folder_card_fid = 47; folder_card_title = getmsg(&editor_message_file, &mesg, 125); folder_card_title2 = NULL; folder_card_desc = getmsg(&editor_message_file, &mesg, 128); } } // 0x43C1B0 int editor_save(File* stream) { if (db_fwriteInt(stream, last_level) == -1) return -1; if (db_fwriteByte(stream, free_perk) == -1) return -1; return 0; } // 0x43C1E0 int editor_load(File* stream) { if (db_freadInt(stream, &last_level) == -1) return -1; if (db_freadByte(stream, &free_perk) == -1) return -1; return 0; } // 0x43C20C void editor_reset() { character_points = 5; last_level = 1; } // level up if needed // // 0x43C228 static int UpdateLevel() { int level = stat_pc_get(PC_STAT_LEVEL); if (level != last_level && level <= PC_LEVEL_MAX) { for (int nextLevel = last_level + 1; nextLevel <= level; nextLevel++) { int sp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); sp += 5; sp += stat_get_base(obj_dude, STAT_INTELLIGENCE) * 2; sp += perk_level(obj_dude, PERK_EDUCATED) * 2; sp += trait_level(TRAIT_SKILLED) * 5; if (trait_level(TRAIT_GIFTED)) { sp -= 5; if (sp < 0) { sp = 0; } } if (sp > 99) { sp = 99; } stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, sp); // NOTE: Uninline. int selectedPerksCount = PerkCount(); if (selectedPerksCount < 37) { int progression = 3; if (trait_level(TRAIT_SKILLED)) { progression += 1; } if (nextLevel % progression == 0) { free_perk = 1; } } } } if (free_perk != 0) { folder = 0; DrawFolder(); win_draw(edit_win); int rc = perks_dialog(); if (rc == -1) { debug_printf("\n *** Error running perks dialog! ***\n"); return -1; } else if (rc == 0) { DrawFolder(); } else if (rc == 1) { DrawFolder(); free_perk = 0; } } last_level = level; return 1; } // 0x43C398 static void RedrwDPrks() { buf_to_buf( pbckgnd + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, pwin_buf + 280, PERK_WINDOW_WIDTH); ListDPerks(); // NOTE: Original code is slightly different, but basically does the same thing. int perk = name_sort_list[crow + cline].value; int perkFrmId = perk_skilldex_fid(perk); char* perkName = perk_name(perk); char* perkDescription = perk_description(perk); char* perkRank = NULL; char perkRankBuffer[32]; int rank = perk_level(obj_dude, perk); if (rank != 0) { sprintf(perkRankBuffer, "(%d)", rank); perkRank = perkRankBuffer; } DrawCard2(perkFrmId, perkName, perkRank, perkDescription); win_draw(pwin); } // 0x43C4F0 static int perks_dialog() { crow = 0; cline = 0; old_fid2 = -1; old_str2[0] = '\0'; frstc_draw2 = false; CacheEntry* backgroundFrmHandle; int backgroundWidth; int backgroundHeight; int fid = art_id(OBJ_TYPE_INTERFACE, 86, 0, 0, 0); pbckgnd = art_lock(fid, &backgroundFrmHandle, &backgroundWidth, &backgroundHeight); if (pbckgnd == NULL) { debug_printf("\n *** Error running perks dialog window ***\n"); return -1; } int perkWindowX = PERK_WINDOW_X; int perkWindowY = PERK_WINDOW_Y; pwin = win_add(perkWindowX, perkWindowY, PERK_WINDOW_WIDTH, PERK_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (pwin == -1) { art_ptr_unlock(backgroundFrmHandle); debug_printf("\n *** Error running perks dialog window ***\n"); return -1; } pwin_buf = win_get_buf(pwin); memcpy(pwin_buf, pbckgnd, PERK_WINDOW_WIDTH * PERK_WINDOW_HEIGHT); int btn; btn = win_register_button(pwin, 48, 186, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 500, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button(pwin, 153, 186, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].width, GInfo[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 502, grphbmp[EDITOR_GRAPHIC_LITTLE_RED_BUTTON_UP], grphbmp[EDITOR_GRAPHIC_LILTTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button(pwin, 25, 46, GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].width, GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].height, -1, 574, 572, 574, grphbmp[EDITOR_GRAPHIC_UP_ARROW_OFF], grphbmp[EDITOR_GRAPHIC_UP_ARROW_ON], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, NULL); } btn = win_register_button(pwin, 25, 47 + GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].height, GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].width, GInfo[EDITOR_GRAPHIC_UP_ARROW_ON].height, -1, 575, 573, 575, grphbmp[EDITOR_GRAPHIC_DOWN_ARROW_OFF], grphbmp[EDITOR_GRAPHIC_DOWN_ARROW_ON], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, NULL); } win_register_button(pwin, PERK_WINDOW_LIST_X, PERK_WINDOW_LIST_Y, PERK_WINDOW_LIST_WIDTH, PERK_WINDOW_LIST_HEIGHT, -1, -1, -1, 501, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); text_font(103); const char* msg; // PICK A NEW PERK msg = getmsg(&editor_message_file, &mesg, 152); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]); // DONE msg = getmsg(&editor_message_file, &mesg, 100); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 186 + 69, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]); // CANCEL msg = getmsg(&editor_message_file, &mesg, 102); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 186 + 171, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]); int count = ListDPerks(); // NOTE: Original code is slightly different, but does the same thing. int perk = name_sort_list[crow + cline].value; int perkFrmId = perk_skilldex_fid(perk); char* perkName = perk_name(perk); char* perkDescription = perk_description(perk); char* perkRank = NULL; char perkRankBuffer[32]; int rank = perk_level(obj_dude, perk); if (rank != 0) { sprintf(perkRankBuffer, "(%d)", rank); perkRank = perkRankBuffer; } DrawCard2(perkFrmId, perkName, perkRank, perkDescription); win_draw(pwin); int rc = InputPDLoop(count, RedrwDPrks); if (rc == 1) { if (perk_add(obj_dude, name_sort_list[crow + cline].value) == -1) { debug_printf("\n*** Unable to add perk! ***\n"); rc = 2; } } rc &= 1; if (rc != 0) { if (perk_level(obj_dude, PERK_TAG) != 0 && perk_back[PERK_TAG] == 0) { if (!Add4thTagSkill()) { perk_sub(obj_dude, PERK_TAG); } } else if (perk_level(obj_dude, PERK_MUTATE) != 0 && perk_back[PERK_MUTATE] == 0) { if (!GetMutateTrait()) { perk_sub(obj_dude, PERK_MUTATE); } } else if (perk_level(obj_dude, PERK_LIFEGIVER) != perk_back[PERK_LIFEGIVER]) { int maxHp = stat_get_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS); stat_set_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS, maxHp + 4); critter_adjust_hits(obj_dude, 4); } else if (perk_level(obj_dude, PERK_EDUCATED) != perk_back[PERK_EDUCATED]) { int sp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, sp + 2); } } ListSkills(0); PrintBasicStat(RENDER_ALL_STATS, 0, 0); PrintLevelWin(); ListDrvdStats(); DrawFolder(); DrawInfoWin(); win_draw(edit_win); art_ptr_unlock(backgroundFrmHandle); win_delete(pwin); return rc; } // 0x43CACC static int InputPDLoop(int count, void (*refreshProc)()) { text_font(101); int v3 = count - 11; int height = text_height(); oldsline = -2; int v16 = height + 2; int v7 = 0; int rc = 0; while (rc == 0) { int keyCode = get_input(); int v19 = 0; if (keyCode == 500) { rc = 1; } else if (keyCode == KEY_RETURN) { gsound_play_sfx_file("ib1p1xx1"); rc = 1; } else if (keyCode == 501) { mouse_get_position(&mouse_xpos, &mouse_ypos); cline = (mouse_ypos - (PERK_WINDOW_Y + PERK_WINDOW_LIST_Y)) / v16; if (cline >= 0) { if (count - 1 < cline) cline = count - 1; } else { cline = 0; } if (cline == oldsline) { gsound_play_sfx_file("ib1p1xx1"); rc = 1; } oldsline = cline; refreshProc(); } else if (keyCode == 502 || keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) { rc = 2; } else { switch (keyCode) { case KEY_ARROW_UP: oldsline = -2; crow--; if (crow < 0) { crow = 0; cline--; if (cline < 0) { cline = 0; } } refreshProc(); break; case KEY_PAGE_UP: oldsline = -2; for (int index = 0; index < 11; index++) { crow--; if (crow < 0) { crow = 0; cline--; if (cline < 0) { cline = 0; } } } refreshProc(); break; case KEY_ARROW_DOWN: oldsline = -2; if (count > 11) { crow++; if (crow > count - 11) { crow = count - 11; cline++; if (cline > 10) { cline = 10; } } } else { cline++; if (cline > count - 1) { cline = count - 1; } } refreshProc(); break; case KEY_PAGE_DOWN: oldsline = -2; for (int index = 0; index < 11; index++) { if (count > 11) { crow++; if (crow > count - 11) { crow = count - 11; cline++; if (cline > 10) { cline = 10; } } } else { cline++; if (cline > count - 1) { cline = count - 1; } } } refreshProc(); break; case 572: _repFtime = 4; oldsline = -2; do { _frame_time = get_time(); if (v19 <= 14.4) { v19++; } if (v19 == 1 || v19 > 14.4) { if (v19 > 14.4) { _repFtime++; if (_repFtime > 24) { _repFtime = 24; } } crow--; if (crow < 0) { crow = 0; cline--; if (cline < 0) { cline = 0; } } refreshProc(); } if (v19 < 14.4) { while (elapsed_time(_frame_time) < 1000 / 24) { } } else { while (elapsed_time(_frame_time) < 1000 / _repFtime) { } } } while (get_input() != 574); break; case 573: oldsline = -2; _repFtime = 4; if (count > 11) { do { _frame_time = get_time(); if (v19 <= 14.4) { v19++; } if (v19 == 1 || v19 > 14.4) { if (v19 > 14.4) { _repFtime++; if (_repFtime > 24) { _repFtime = 24; } } crow++; if (crow > count - 11) { crow = count - 11; cline++; if (cline > 10) { cline = 10; } } refreshProc(); } if (v19 < 14.4) { while (elapsed_time(_frame_time) < 1000 / 24) { } } else { while (elapsed_time(_frame_time) < 1000 / _repFtime) { } } } while (get_input() != 575); } else { do { _frame_time = get_time(); if (v19 <= 14.4) { v19++; } if (v19 == 1 || v19 > 14.4) { if (v19 > 14.4) { _repFtime++; if (_repFtime > 24) { _repFtime = 24; } } cline++; if (cline > count - 1) { cline = count - 1; } refreshProc(); } if (v19 < 14.4) { while (elapsed_time(_frame_time) < 1000 / 24) { } } else { while (elapsed_time(_frame_time) < 1000 / _repFtime) { } } } while (get_input() != 575); } break; case KEY_HOME: crow = 0; cline = 0; oldsline = -2; refreshProc(); break; case KEY_END: oldsline = -2; if (count > 11) { crow = count - 11; cline = 10; } else { cline = count - 1; } refreshProc(); break; default: if (elapsed_time(_frame_time) > 700) { _frame_time = get_time(); oldsline = -2; } break; } } } return rc; } // 0x43D0BC static int ListDPerks() { buf_to_buf( pbckgnd + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH); text_font(101); int perks[PERK_COUNT]; int count = perk_make_list(obj_dude, perks); if (count == 0) { return 0; } for (int perk = 0; perk < PERK_COUNT; perk++) { name_sort_list[perk].value = 0; name_sort_list[perk].name = NULL; } for (int index = 0; index < count; index++) { name_sort_list[index].value = perks[index]; name_sort_list[index].name = perk_name(perks[index]); } qsort(name_sort_list, count, sizeof(*name_sort_list), name_sort_comp); int v16 = count - crow; if (v16 > 11) { v16 = 11; } v16 += crow; int y = 43; int yStep = text_height() + 2; for (int index = crow; index < v16; index++) { int color; if (index == crow + cline) { color = colorTable[32747]; } else { color = colorTable[992]; } text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); if (perk_level(obj_dude, name_sort_list[index].value) != 0) { char rankString[256]; sprintf(rankString, "(%d)", perk_level(obj_dude, name_sort_list[index].value)); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 207, rankString, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); } y += yStep; } return count; } // 0x43D2F8 void RedrwDMPrk() { buf_to_buf(pbckgnd + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, pwin_buf + 280, PERK_WINDOW_WIDTH); ListMyTraits(optrt_count); char* traitName = name_sort_list[crow + cline].name; char* tratDescription = trait_description(name_sort_list[crow + cline].value); int frmId = trait_pic(name_sort_list[crow + cline].value); DrawCard2(frmId, traitName, NULL, tratDescription); win_draw(pwin); } // 0x43D38C static bool GetMutateTrait() { old_fid2 = -1; old_str2[0] = '\0'; frstc_draw2 = false; // NOTE: Uninline. trait_count = TRAITS_MAX_SELECTED_COUNT - get_trait_count(); bool result = true; if (trait_count >= 1) { text_font(103); buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 14 + 49, 206, text_height() + 2, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 15 + 49, PERK_WINDOW_WIDTH); // LOSE A TRAIT char* msg = getmsg(&editor_message_file, &mesg, 154); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]); optrt_count = 0; cline = 0; crow = 0; RedrwDMPrk(); int rc = InputPDLoop(trait_count, RedrwDMPrk); if (rc == 1) { if (cline == 0) { if (trait_count == 1) { temp_trait[0] = -1; temp_trait[1] = -1; } else { if (name_sort_list[0].value == temp_trait[0]) { temp_trait[0] = temp_trait[1]; temp_trait[1] = -1; } else { temp_trait[1] = -1; } } } else { if (name_sort_list[0].value == temp_trait[0]) { temp_trait[1] = -1; } else { temp_trait[0] = temp_trait[1]; temp_trait[1] = -1; } } } else { result = false; } } if (result) { text_font(103); buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 14 + 49, 206, text_height() + 2, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 15 + 49, PERK_WINDOW_WIDTH); // PICK A NEW TRAIT char* msg = getmsg(&editor_message_file, &mesg, 153); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 16 + 49, msg, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[18979]); cline = 0; crow = 0; optrt_count = 1; RedrwDMPrk(); int count = 16 - trait_count; if (count > 16) { count = 16; } int rc = InputPDLoop(count, RedrwDMPrk); if (rc == 1) { if (trait_count != 0) { temp_trait[1] = name_sort_list[cline + crow].value; } else { temp_trait[0] = name_sort_list[cline + crow].value; temp_trait[1] = -1; } trait_set(temp_trait[0], temp_trait[1]); } else { result = false; } } if (!result) { memcpy(temp_trait, trait_back, sizeof(temp_trait)); } return result; } // 0x43D668 static void RedrwDMTagSkl() { buf_to_buf(pbckgnd + 280, 293, PERK_WINDOW_HEIGHT, PERK_WINDOW_WIDTH, pwin_buf + 280, PERK_WINDOW_WIDTH); ListNewTagSkills(); char* name = name_sort_list[crow + cline].name; char* description = skill_description(name_sort_list[crow + cline].value); int frmId = skill_pic(name_sort_list[crow + cline].value); DrawCard2(frmId, name, NULL, description); win_draw(pwin); } // 0x43D6F8 static bool Add4thTagSkill() { text_font(103); buf_to_buf(pbckgnd + 573 * 14 + 49, 206, text_height() + 2, 573, pwin_buf + 573 * 15 + 49, 573); // PICK A NEW TAG SKILL char* messageListItemText = getmsg(&editor_message_file, &mesg, 155); text_to_buf(pwin_buf + 573 * 16 + 49, messageListItemText, 573, 573, colorTable[18979]); cline = 0; crow = 0; old_fid2 = -1; old_str2[0] = '\0'; frstc_draw2 = false; RedrwDMTagSkl(); int rc = InputPDLoop(optrt_count, RedrwDMTagSkl); if (rc != 1) { memcpy(temp_tag_skill, tag_skill_back, sizeof(temp_tag_skill)); skill_set_tags(tag_skill_back, NUM_TAGGED_SKILLS); return false; } temp_tag_skill[3] = name_sort_list[crow + cline].value; skill_set_tags(temp_tag_skill, NUM_TAGGED_SKILLS); return true; } // 0x43D81C static void ListNewTagSkills() { buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH); text_font(101); optrt_count = 0; int y = 43; int yStep = text_height() + 2; for (int skill = 0; skill < SKILL_COUNT; skill++) { if (skill != temp_tag_skill[0] && skill != temp_tag_skill[1] && skill != temp_tag_skill[2] && skill != temp_tag_skill[3]) { name_sort_list[optrt_count].value = skill; name_sort_list[optrt_count].name = skill_name(skill); optrt_count++; } } qsort(name_sort_list, optrt_count, sizeof(*name_sort_list), name_sort_comp); for (int index = crow; index < crow + 11; index++) { int color; if (index == cline + crow) { color = colorTable[32747]; } else { color = colorTable[992]; } text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); y += yStep; } } // 0x43D960 static int ListMyTraits(int a1) { buf_to_buf(pbckgnd + PERK_WINDOW_WIDTH * 43 + 45, 192, 129, PERK_WINDOW_WIDTH, pwin_buf + PERK_WINDOW_WIDTH * 43 + 45, PERK_WINDOW_WIDTH); text_font(101); int y = 43; int yStep = text_height() + 2; if (a1 != 0) { int count = 0; for (int trait = 0; trait < TRAIT_COUNT; trait++) { if (trait != trait_back[0] && trait != trait_back[1]) { name_sort_list[count].value = trait; name_sort_list[count].name = trait_name(trait); count++; } } qsort(name_sort_list, count, sizeof(*name_sort_list), name_sort_comp); for (int index = crow; index < crow + 11; index++) { int color; if (index == cline + crow) { color = colorTable[32747]; } else { color = colorTable[992]; } text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); y += yStep; } } else { // NOTE: Original code does not use loop. for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) { name_sort_list[index].value = temp_trait[index]; name_sort_list[index].name = trait_name(temp_trait[index]); } if (trait_count > 1) { qsort(name_sort_list, trait_count, sizeof(*name_sort_list), name_sort_comp); } for (int index = 0; index < trait_count; index++) { int color; if (index == cline) { color = colorTable[32747]; } else { color = colorTable[992]; } text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 45, name_sort_list[index].name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, color); y += yStep; } } return 0; } // 0x43DB48 static int name_sort_comp(const void* a1, const void* a2) { PerkDialogOption* v1 = (PerkDialogOption*)a1; PerkDialogOption* v2 = (PerkDialogOption*)a2; return strcmp(v1->name, v2->name); } // 0x43DB54 static int DrawCard2(int frmId, const char* name, const char* rank, char* description) { int fid = art_id(OBJ_TYPE_SKILLDEX, frmId, 0, 0, 0); CacheEntry* handle; int width; int height; unsigned char* data = art_lock(fid, &handle, &width, &height); if (data == NULL) { return -1; } buf_to_buf(data, width, height, width, pwin_buf + PERK_WINDOW_WIDTH * 64 + 413, PERK_WINDOW_WIDTH); // Calculate width of transparent pixels on the left side of the image. This // space will be occupied by description (in addition to fixed width). int extraDescriptionWidth = 150; for (int y = 0; y < height; y++) { unsigned char* stride = data; for (int x = 0; x < width; x++) { if (HighRGB(*stride) < 2) { if (extraDescriptionWidth > x) { extraDescriptionWidth = x; } } stride++; } data += width; } // Add gap between description and image. extraDescriptionWidth -= 8; if (extraDescriptionWidth < 0) { extraDescriptionWidth = 0; } text_font(102); int nameHeight = text_height(); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * 27 + 280, name, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[0]); if (rank != NULL) { int rankX = text_width(name) + 280 + 8; text_font(101); int rankHeight = text_height(); text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * (23 + nameHeight - rankHeight) + rankX, rank, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[0]); } win_line(pwin, 280, 27 + nameHeight, 545, 27 + nameHeight, colorTable[0]); win_line(pwin, 280, 28 + nameHeight, 545, 28 + nameHeight, colorTable[0]); text_font(101); int yStep = text_height() + 1; int y = 70; short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (word_wrap(description, 133 + extraDescriptionWidth, beginnings, &count) != 0) { // FIXME: Leaks handle. return -1; } for (int index = 0; index < count - 1; index++) { char* beginning = description + beginnings[index]; char* ending = description + beginnings[index + 1]; char ch = *ending; *ending = '\0'; text_to_buf(pwin_buf + PERK_WINDOW_WIDTH * y + 280, beginning, PERK_WINDOW_WIDTH, PERK_WINDOW_WIDTH, colorTable[0]); *ending = ch; y += yStep; } if (frmId != old_fid2 || strcmp(old_str2, name) != 0) { if (frstc_draw2) { gsound_play_sfx_file("isdxxxx1"); } } strcpy(old_str2, name); old_fid2 = frmId; frstc_draw2 = true; art_ptr_unlock(handle); return 0; } // 0x43DE94 static void push_perks() { int perk; for (perk = 0; perk < PERK_COUNT; perk++) { perk_back[perk] = perk_level(obj_dude, perk); } } // copy editor perks to character // // 0x43DEBC static void pop_perks() { for (int perk = 0; perk < PERK_COUNT; perk++) { for (;;) { int rank = perk_level(obj_dude, perk); if (rank <= perk_back[perk]) { break; } perk_sub(obj_dude, perk); } } for (int i = 0; i < PERK_COUNT; i++) { for (;;) { int rank = perk_level(obj_dude, i); if (rank >= perk_back[i]) { break; } perk_add(obj_dude, i); } } } // NOTE: Inlined. // // 0x43DF24 static int PerkCount() { int perk; int perkCount; perkCount = 0; for (perk = 0; perk < PERK_COUNT; perk++) { if (perk_level(obj_dude, perk) > 0) { perkCount++; if (perkCount >= 37) { break; } } } return perkCount; } // validate SPECIAL stats are <= 10 // // 0x43DF50 static int is_supper_bonus() { for (int stat = 0; stat < 7; stat++) { int v1 = stat_get_base(obj_dude, stat); int v2 = stat_get_bonus(obj_dude, stat); if (v1 + v2 > 10) { return 1; } } return 0; } // 0x43DF8C static int folder_init() { folder_karma_top_line = 0; folder_perk_top_line = 0; folder_kills_top_line = 0; if (folder_up_button == -1) { folder_up_button = win_register_button(edit_win, 317, 364, GInfo[22].width, GInfo[22].height, -1, -1, -1, 17000, grphbmp[21], grphbmp[22], NULL, 32); if (folder_up_button == -1) { return -1; } win_register_button_sound_func(folder_up_button, gsound_red_butt_press, NULL); } if (folder_down_button == -1) { folder_down_button = win_register_button(edit_win, 317, 365 + GInfo[22].height, GInfo[4].width, GInfo[4].height, folder_down_button, folder_down_button, folder_down_button, 17001, grphbmp[3], grphbmp[4], 0, 32); if (folder_down_button == -1) { win_delete_button(folder_up_button); return -1; } win_register_button_sound_func(folder_down_button, gsound_red_butt_press, NULL); } return 0; } // NOTE: Inlined. // // 0x43E090 static void folder_exit() { if (folder_down_button != -1) { win_delete_button(folder_down_button); folder_down_button = -1; } if (folder_up_button != -1) { win_delete_button(folder_up_button); folder_up_button = -1; } } // 0x43E0D4 static void folder_scroll(int direction) { int* v1; switch (folder) { case EDITOR_FOLDER_PERKS: v1 = &folder_perk_top_line; break; case EDITOR_FOLDER_KARMA: v1 = &folder_karma_top_line; break; case EDITOR_FOLDER_KILLS: v1 = &folder_kills_top_line; break; default: return; } if (direction >= 0) { if (folder_max_lines + folder_top_line <= folder_line) { folder_top_line++; if (info_line >= 10 && info_line < 43 && info_line != 10) { info_line--; } } } else { if (folder_top_line > 0) { folder_top_line--; if (info_line >= 10 && info_line < 43 && folder_max_lines + 9 > info_line) { info_line++; } } } *v1 = folder_top_line; DrawFolder(); if (info_line >= 10 && info_line < 43) { buf_to_buf( bckgnd + 640 * 267 + 345, 277, 170, 640, win_buf + 640 * 267 + 345, 640); DrawCard(folder_card_fid, folder_card_title, folder_card_title2, folder_card_desc); } } // 0x43E200 static void folder_clear() { int v0; folder_line = 0; folder_ypos = 364; v0 = text_height(); folder_max_lines = 9; folder_yoffset = v0 + 1; if (info_line < 10 || info_line >= 43) folder_highlight_line = -1; else folder_highlight_line = info_line - 10; if (folder < 1) { if (folder) return; folder_top_line = folder_perk_top_line; } else if (folder == 1) { folder_top_line = folder_karma_top_line; } else if (folder == 2) { folder_top_line = folder_kills_top_line; } } // render heading string with line // // 0x43E28C static int folder_print_seperator(const char* string) { int lineHeight; int x; int y; int lineLen; int gap; int v8 = 0; if (folder_max_lines + folder_top_line > folder_line) { if (folder_line >= folder_top_line) { if (folder_line - folder_top_line == folder_highlight_line) { v8 = 1; } lineHeight = text_height(); x = 280; y = folder_ypos + lineHeight / 2; if (string != NULL) { gap = text_spacing(); // TODO: Not sure about this. lineLen = text_width(string) + gap * 4; x = (x - lineLen) / 2; text_to_buf(win_buf + 640 * folder_ypos + 34 + x + gap * 2, string, 640, 640, colorTable[992]); win_line(edit_win, 34 + x + lineLen, y, 34 + 280, y, colorTable[992]); } win_line(edit_win, 34, y, 34 + x, y, colorTable[992]); folder_ypos += folder_yoffset; } folder_line++; return v8; } else { return 0; } } // 0x43E3D8 static bool folder_print_line(const char* string) { bool success = false; int color; if (folder_max_lines + folder_top_line > folder_line) { if (folder_line >= folder_top_line) { if (folder_line - folder_top_line == folder_highlight_line) { success = true; color = colorTable[32747]; } else { color = colorTable[992]; } text_to_buf(win_buf + 640 * folder_ypos + 34, string, 640, 640, color); folder_ypos += folder_yoffset; } folder_line++; } return success; } // 0x43E470 static bool folder_print_kill(const char* name, int kills) { char killsString[8]; int color; int gap; bool success = false; if (folder_max_lines + folder_top_line > folder_line) { if (folder_line >= folder_top_line) { if (folder_line - folder_top_line == folder_highlight_line) { color = colorTable[32747]; success = true; } else { color = colorTable[992]; } itoa(kills, killsString, 10); int v6 = text_width(killsString); // TODO: Check. gap = text_spacing(); int v11 = folder_ypos + text_height() / 2; text_to_buf(win_buf + 640 * folder_ypos + 34, name, 640, 640, color); int v12 = text_width(name); win_line(edit_win, 34 + v12 + gap, v11, 314 - v6 - gap, v11, color); text_to_buf(win_buf + 640 * folder_ypos + 314 - v6, killsString, 640, 640, color); folder_ypos += folder_yoffset; } folder_line++; } return success; } // 0x43E5C4 static int karma_vars_init() { const char* delim = " \t,"; if (karma_vars != NULL) { mem_free(karma_vars); karma_vars = NULL; } karma_vars_count = 0; File* stream = db_fopen("data\\karmavar.txt", "rt"); if (stream == NULL) { return -1; } char string[256]; while (db_fgets(string, 256, stream)) { KarmaEntry entry; char* pch = string; while (isspace(*pch & 0xFF)) { pch++; } if (*pch == '#') { continue; } char* tok = strtok(pch, delim); if (tok == NULL) { continue; } entry.gvar = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.art_num = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.name = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.description = atoi(tok); KarmaEntry* entries = (KarmaEntry*)mem_realloc(karma_vars, sizeof(*entries) * (karma_vars_count + 1)); if (entries == NULL) { db_fclose(stream); return -1; } memcpy(&(entries[karma_vars_count]), &entry, sizeof(entry)); karma_vars = entries; karma_vars_count++; } qsort(karma_vars, karma_vars_count, sizeof(*karma_vars), karma_vars_qsort_compare); db_fclose(stream); return 0; } // NOTE: Inlined. // // 0x43E764 static void karma_vars_exit() { if (karma_vars != NULL) { mem_free(karma_vars); karma_vars = NULL; } karma_vars_count = 0; } // 0x43E78C static int karma_vars_qsort_compare(const void* a1, const void* a2) { KarmaEntry* v1 = (KarmaEntry*)a1; KarmaEntry* v2 = (KarmaEntry*)a2; return v1->gvar - v2->gvar; } // 0x43E798 static int general_reps_init() { const char* delim = " \t,"; if (general_reps != NULL) { mem_free(general_reps); general_reps = NULL; } general_reps_count = 0; File* stream = db_fopen("data\\genrep.txt", "rt"); if (stream == NULL) { return -1; } char string[256]; while (db_fgets(string, 256, stream)) { GenericReputationEntry entry; char* pch = string; while (isspace(*pch & 0xFF)) { pch++; } if (*pch == '#') { continue; } char* tok = strtok(pch, delim); if (tok == NULL) { continue; } entry.threshold = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.name = atoi(tok); GenericReputationEntry* entries = (GenericReputationEntry*)mem_realloc(general_reps, sizeof(*entries) * (general_reps_count + 1)); if (entries == NULL) { db_fclose(stream); return -1; } memcpy(&(entries[general_reps_count]), &entry, sizeof(entry)); general_reps = entries; general_reps_count++; } qsort(general_reps, general_reps_count, sizeof(*general_reps), general_reps_qsort_compare); db_fclose(stream); return 0; } // NOTE: Inlined. // // 0x43E914 static void general_reps_exit() { if (general_reps != NULL) { mem_free(general_reps); general_reps = NULL; } general_reps_count = 0; } // 0x43E93C static int general_reps_qsort_compare(const void* a1, const void* a2) { GenericReputationEntry* v1 = (GenericReputationEntry*)a1; GenericReputationEntry* v2 = (GenericReputationEntry*)a2; if (v2->threshold > v1->threshold) { return 1; } else if (v2->threshold < v1->threshold) { return -1; } return 0; } ================================================ FILE: src/game/editor.h ================================================ #ifndef FALLOUT_GAME_EDITOR_H_ #define FALLOUT_GAME_EDITOR_H_ #include "plib/db/db.h" #define TOWN_REPUTATION_COUNT 19 #define ADDICTION_REPUTATION_COUNT 8 typedef struct TownReputationEntry { int gvar; int city; } TownReputationEntry; extern int character_points; extern TownReputationEntry town_rep_info[TOWN_REPUTATION_COUNT]; extern int addiction_vars[ADDICTION_REPUTATION_COUNT]; extern int addiction_pics[ADDICTION_REPUTATION_COUNT]; int editor_design(bool isCreationMode); void CharEditInit(); int get_input_str(int win, int cancelKeyCode, char* text, int maxLength, int x, int y, int textColor, int backgroundColor, int flags); bool isdoschar(int ch); char* strmfe(char* dest, const char* name, const char* ext); bool db_access(const char* fname); char* AddSpaces(char* string, int length); char* itostndn(int value, char* dest); int editor_save(File* stream); int editor_load(File* stream); void editor_reset(); void RedrwDMPrk(); #endif /* FALLOUT_GAME_EDITOR_H_ */ ================================================ FILE: src/game/elevator.c ================================================ #include "game/elevator.h" #include #include #include "plib/gnw/input.h" #include "game/art.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/gmouse.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/map.h" #include "game/pipboy.h" #include "game/scripts.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" // The maximum number of elevator levels. #define ELEVATOR_LEVEL_MAX 4 // NOTE: There are two variables which hold background data used in the // elevator window - [grphbmp[ELEVATOR_FRM_BACKGROUND]] and [grphbmp[ELEVATOR_FRM_PANEL]]. // For unknown reason they are using -1 to denote that they are not set // (instead of using NULL). #define ELEVATOR_BACKGROUND_NULL ((unsigned char*)(-1)) // NOTE: This enum is a little bit unsual. It contains two types of members: // static and dynamic. Static part of enum is always accessed with array // operations as commonly seen in other UI setup/teardown loops. Background and // panel are always accessed separately without using loops, but they are a part // of globals holding state (buffers, cache keys, dimensions). typedef enum ElevatorFrm { ELEVATOR_FRM_BUTTON_DOWN, ELEVATOR_FRM_BUTTON_UP, ELEVATOR_FRM_GAUGE, ELEVATOR_FRM_BACKGROUND, ELEVATOR_FRM_PANEL, ELEVATOR_FRM_COUNT, ELEVATOR_FRM_STATIC_COUNT = 3, } ElevatorFrm; typedef struct ElevatorBackground { int backgroundFrmId; int panelFrmId; } ElevatorBackground; typedef struct ElevatorDescription { int map; int elevation; int tile; } ElevatorDescription; static int elevator_start(int elevator); static void elevator_end(); static int Check4Keys(int elevator, int keyCode); // 0x43E950 static const int grph_id[ELEVATOR_FRM_STATIC_COUNT] = { 141, // ebut_in.frm - map elevator screen 142, // ebut_out.frm - map elevator screen 149, // gaj000.frm - map elevator screen }; // 0x43E95C static const ElevatorBackground intotal[ELEVATOR_COUNT] = { { 143, -1 }, { 143, 150 }, { 144, -1 }, { 144, 145 }, { 146, -1 }, { 146, 147 }, { 146, -1 }, { 146, 151 }, { 148, -1 }, { 146, -1 }, { 146, -1 }, { 146, 147 }, { 388, -1 }, { 143, 150 }, { 148, -1 }, { 148, -1 }, { 148, -1 }, { 143, 150 }, { 143, 150 }, { 143, 150 }, { 143, 150 }, { 143, 150 }, { 143, 150 }, { 143, 150 }, }; // Number of levels for eleveators. // // 0x43EA1C static const int btncnt[ELEVATOR_COUNT] = { 4, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 2, 4, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, }; // 0x43EA7C static const ElevatorDescription retvals[ELEVATOR_COUNT][ELEVATOR_LEVEL_MAX] = { { { 14, 0, 18940 }, { 14, 1, 18936 }, { 15, 0, 21340 }, { 15, 1, 21340 }, }, { { 13, 0, 20502 }, { 14, 0, 14912 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 33, 0, 12498 }, { 33, 1, 20094 }, { 34, 0, 17312 }, { 0, 0, -1 }, }, { { 34, 0, 16140 }, { 34, 1, 16140 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 49, 0, 14920 }, { 49, 1, 15120 }, { 50, 0, 12944 }, { 0, 0, -1 }, }, { { 50, 0, 24520 }, { 50, 1, 25316 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 42, 0, 22526 }, { 42, 1, 22526 }, { 42, 2, 22526 }, { 0, 0, -1 }, }, { { 42, 2, 14086 }, { 43, 0, 14086 }, { 43, 2, 14086 }, { 0, 0, -1 }, }, { { 40, 0, 14104 }, { 40, 1, 22504 }, { 40, 2, 17312 }, { 0, 0, -1 }, }, { { 9, 0, 13704 }, { 9, 1, 23302 }, { 9, 2, 17308 }, { 0, 0, -1 }, }, { { 28, 0, 19300 }, { 28, 1, 19300 }, { 28, 2, 20110 }, { 0, 0, -1 }, }, { { 28, 2, 20118 }, { 29, 0, 21710 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 28, 0, 20122 }, { 28, 1, 20124 }, { 28, 2, 20940 }, { 29, 0, 22540 }, }, { { 12, 1, 16052 }, { 12, 2, 14480 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 6, 0, 14104 }, { 6, 1, 22504 }, { 6, 2, 17312 }, { 0, 0, -1 }, }, { { 30, 0, 14104 }, { 30, 1, 22504 }, { 30, 2, 17312 }, { 0, 0, -1 }, }, { { 36, 0, 13704 }, { 36, 1, 23302 }, { 36, 2, 17308 }, { 0, 0, -1 }, }, { { 39, 0, 17285 }, { 36, 0, 19472 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 109, 2, 10701 }, { 109, 1, 10705 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 109, 2, 14697 }, { 109, 1, 15099 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 109, 2, 23877 }, { 109, 1, 25481 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 109, 2, 26130 }, { 109, 1, 24721 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 137, 0, 23953 }, { 148, 1, 16526 }, { 0, 0, -1 }, { 0, 0, -1 }, }, { { 62, 0, 13901 }, { 63, 1, 17923 }, { 0, 0, -1 }, { 0, 0, -1 }, }, }; // NOTE: These values are also used as key bindings. // // 0x43EEFC static const char keytable[ELEVATOR_COUNT][ELEVATOR_LEVEL_MAX] = { { '1', '2', '3', '4' }, { 'G', '1', '\0', '\0' }, { '1', '2', '3', '\0' }, { '3', '4', '\0', '\0' }, { '1', '2', '3', '\0' }, { '3', '4', '\0', '\0' }, { '1', '2', '3', '\0' }, { '3', '4', '6', '\0' }, { '1', '2', '3', '\0' }, { '1', '2', '3', '\0' }, { '1', '2', '3', '\0' }, { '3', '4', '\0', '\0' }, { '1', '2', '3', '4' }, { '1', '2', '\0', '\0' }, { '1', '2', '3', '\0' }, { '1', '2', '3', '\0' }, { '1', '2', '3', '\0' }, { '1', '2', '\0', '\0' }, { '1', '2', '\0', '\0' }, { '1', '2', '\0', '\0' }, { '1', '2', '\0', '\0' }, { '1', '2', '\0', '\0' }, { '1', '2', '\0', '\0' }, { '1', '2', '\0', '\0' }, }; // 0x51862C static const char* sfxtable[ELEVATOR_LEVEL_MAX - 1][ELEVATOR_LEVEL_MAX] = { { "ELV1_1", "ELV1_1", "ERROR", "ERROR", }, { "ELV1_2", "ELV1_2", "ELV1_1", "ERROR", }, { "ELV1_3", "ELV1_3", "ELV2_3", "ELV1_1", }, }; // 0x570A2C static Size GInfo[ELEVATOR_FRM_COUNT]; // 0x570A54 static int elev_win; // 0x570A58 static CacheEntry* grph_key[ELEVATOR_FRM_COUNT]; // 0x570A6C static unsigned char* win_buf; // 0x570A70 static bool bk_enable; // 0x570A74 static unsigned char* grphbmp[ELEVATOR_FRM_COUNT]; // Presents elevator dialog for player to pick a desired level. // // 0x43EF5C int elevator_select(int elevator, int* mapPtr, int* elevationPtr, int* tilePtr) { if (elevator < 0 || elevator >= ELEVATOR_COUNT) { return -1; } if (elevator_start(elevator) == -1) { return -1; } const ElevatorDescription* elevatorDescription = retvals[elevator]; int index; for (index = 0; index < ELEVATOR_LEVEL_MAX; index++) { if (elevatorDescription[index].map == *mapPtr) { break; } } if (index < ELEVATOR_LEVEL_MAX) { if (elevatorDescription[*elevationPtr + index].tile != -1) { *elevationPtr += index; } } if (elevator == ELEVATOR_SIERRA_2) { if (*elevationPtr <= 2) { *elevationPtr -= 2; } else { *elevationPtr -= 3; } } else if (elevator == ELEVATOR_MILITARY_BASE_LOWER) { if (*elevationPtr >= 2) { *elevationPtr -= 2; } } else if (elevator == ELEVATOR_MILITARY_BASE_UPPER && *elevationPtr == 4) { *elevationPtr -= 2; } if (*elevationPtr > 3) { *elevationPtr -= 3; } debug_printf("\n the start elev level %d\n", *elevationPtr); int v18 = (GInfo[ELEVATOR_FRM_GAUGE].width * GInfo[ELEVATOR_FRM_GAUGE].height) / 13; float v42 = 12.0f / (float)(btncnt[elevator] - 1); buf_to_buf( grphbmp[ELEVATOR_FRM_GAUGE] + v18 * (int)((float)(*elevationPtr) * v42), GInfo[ELEVATOR_FRM_GAUGE].width, GInfo[ELEVATOR_FRM_GAUGE].height / 13, GInfo[ELEVATOR_FRM_GAUGE].width, win_buf + GInfo[ELEVATOR_FRM_BACKGROUND].width * 41 + 121, GInfo[ELEVATOR_FRM_BACKGROUND].width); win_draw(elev_win); bool done = false; int keyCode; while (!done) { keyCode = get_input(); if (keyCode == KEY_ESCAPE) { done = true; } if (keyCode >= 500 && keyCode < 504) { done = true; } if (keyCode > 0 && keyCode < 500) { int level = Check4Keys(elevator, keyCode); if (level != 0) { keyCode = 500 + level - 1; done = true; } } } if (keyCode != KEY_ESCAPE) { keyCode -= 500; if (*elevationPtr != keyCode) { float v43 = (float)(btncnt[elevator] - 1) / 12.0f; unsigned int delay = (unsigned int)(v43 * 276.92307); if (keyCode < *elevationPtr) { v43 = -v43; } int numberOfLevelsTravelled = keyCode - *elevationPtr; if (numberOfLevelsTravelled < 0) { numberOfLevelsTravelled = -numberOfLevelsTravelled; } gsound_play_sfx_file(sfxtable[btncnt[elevator] - 2][numberOfLevelsTravelled]); float v41 = (float)keyCode * v42; float v44 = (float)(*elevationPtr) * v42; do { unsigned int tick = get_time(); v44 += v43; buf_to_buf( grphbmp[ELEVATOR_FRM_GAUGE] + v18 * (int)v44, GInfo[ELEVATOR_FRM_GAUGE].width, GInfo[ELEVATOR_FRM_GAUGE].height / 13, GInfo[ELEVATOR_FRM_GAUGE].width, win_buf + GInfo[ELEVATOR_FRM_BACKGROUND].width * 41 + 121, GInfo[ELEVATOR_FRM_BACKGROUND].width); win_draw(elev_win); while (elapsed_time(tick) < delay) { } } while ((v43 <= 0.0 || v44 < v41) && (v43 > 0.0 || v44 > v41)); pause_for_tocks(200); } } elevator_end(); if (keyCode != KEY_ESCAPE) { const ElevatorDescription* description = &(elevatorDescription[keyCode]); *mapPtr = description->map; *elevationPtr = description->elevation; *tilePtr = description->tile; } return 0; } // 0x43F324 static int elevator_start(int elevator) { bk_enable = map_disable_bk_processes(); cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); scr_disable(); int index; for (index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, grph_id[index], 0, 0, 0); grphbmp[index] = art_lock(fid, &(grph_key[index]), &(GInfo[index].width), &(GInfo[index].height)); if (grphbmp[index] == NULL) { break; } } if (index != ELEVATOR_FRM_STATIC_COUNT) { for (int reversedIndex = index - 1; reversedIndex >= 0; reversedIndex--) { art_ptr_unlock(grph_key[reversedIndex]); } if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return -1; } grphbmp[ELEVATOR_FRM_PANEL] = ELEVATOR_BACKGROUND_NULL; grphbmp[ELEVATOR_FRM_BACKGROUND] = ELEVATOR_BACKGROUND_NULL; const ElevatorBackground* elevatorBackground = &(intotal[elevator]); bool backgroundsLoaded = true; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, elevatorBackground->backgroundFrmId, 0, 0, 0); grphbmp[ELEVATOR_FRM_BACKGROUND] = art_lock(backgroundFid, &grph_key[ELEVATOR_FRM_BACKGROUND], &(GInfo[ELEVATOR_FRM_BACKGROUND].width), &(GInfo[ELEVATOR_FRM_BACKGROUND].height)); if (grphbmp[ELEVATOR_FRM_BACKGROUND] != NULL) { if (elevatorBackground->panelFrmId != -1) { int panelFid = art_id(OBJ_TYPE_INTERFACE, elevatorBackground->panelFrmId, 0, 0, 0); grphbmp[ELEVATOR_FRM_PANEL] = art_lock(panelFid, &grph_key[ELEVATOR_FRM_PANEL], &(GInfo[ELEVATOR_FRM_PANEL].width), &(GInfo[ELEVATOR_FRM_PANEL].height)); if (grphbmp[ELEVATOR_FRM_PANEL] == NULL) { grphbmp[ELEVATOR_FRM_PANEL] = ELEVATOR_BACKGROUND_NULL; backgroundsLoaded = false; } } } else { grphbmp[ELEVATOR_FRM_BACKGROUND] = ELEVATOR_BACKGROUND_NULL; backgroundsLoaded = false; } if (!backgroundsLoaded) { if (grphbmp[ELEVATOR_FRM_BACKGROUND] != ELEVATOR_BACKGROUND_NULL) { art_ptr_unlock(grph_key[ELEVATOR_FRM_BACKGROUND]); } if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) { art_ptr_unlock(grph_key[ELEVATOR_FRM_PANEL]); } for (int index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) { art_ptr_unlock(grph_key[index]); } if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return -1; } int elevatorWindowX = (640 - GInfo[ELEVATOR_FRM_BACKGROUND].width) / 2; int elevatorWindowY = (480 - INTERFACE_BAR_HEIGHT - 1 - GInfo[ELEVATOR_FRM_BACKGROUND].height) / 2; elev_win = win_add( elevatorWindowX, elevatorWindowY, GInfo[ELEVATOR_FRM_BACKGROUND].width, GInfo[ELEVATOR_FRM_BACKGROUND].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (elev_win == -1) { if (grphbmp[ELEVATOR_FRM_BACKGROUND] != ELEVATOR_BACKGROUND_NULL) { art_ptr_unlock(grph_key[ELEVATOR_FRM_BACKGROUND]); } if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) { art_ptr_unlock(grph_key[ELEVATOR_FRM_PANEL]); } for (int index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) { art_ptr_unlock(grph_key[index]); } if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return -1; } win_buf = win_get_buf(elev_win); memcpy(win_buf, (unsigned char*)grphbmp[ELEVATOR_FRM_BACKGROUND], GInfo[ELEVATOR_FRM_BACKGROUND].width * GInfo[ELEVATOR_FRM_BACKGROUND].height); if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) { buf_to_buf((unsigned char*)grphbmp[ELEVATOR_FRM_PANEL], GInfo[ELEVATOR_FRM_PANEL].width, GInfo[ELEVATOR_FRM_PANEL].height, GInfo[ELEVATOR_FRM_PANEL].width, win_buf + GInfo[ELEVATOR_FRM_BACKGROUND].width * (GInfo[ELEVATOR_FRM_BACKGROUND].height - GInfo[ELEVATOR_FRM_PANEL].height), GInfo[ELEVATOR_FRM_BACKGROUND].width); } int y = 40; for (int level = 0; level < btncnt[elevator]; level++) { int btn = win_register_button(elev_win, 13, y, GInfo[ELEVATOR_FRM_BUTTON_DOWN].width, GInfo[ELEVATOR_FRM_BUTTON_DOWN].height, -1, -1, -1, 500 + level, grphbmp[ELEVATOR_FRM_BUTTON_UP], grphbmp[ELEVATOR_FRM_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, NULL); } y += 60; } return 0; } // 0x43F6D0 static void elevator_end() { win_delete(elev_win); if (grphbmp[ELEVATOR_FRM_BACKGROUND] != ELEVATOR_BACKGROUND_NULL) { art_ptr_unlock(grph_key[ELEVATOR_FRM_BACKGROUND]); } if (grphbmp[ELEVATOR_FRM_PANEL] != ELEVATOR_BACKGROUND_NULL) { art_ptr_unlock(grph_key[ELEVATOR_FRM_PANEL]); } for (int index = 0; index < ELEVATOR_FRM_STATIC_COUNT; index++) { art_ptr_unlock(grph_key[index]); } scr_enable(); if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); } // 0x43F73C static int Check4Keys(int elevator, int keyCode) { // TODO: Check if result is really unused? toupper(keyCode); for (int index = 0; index < ELEVATOR_LEVEL_MAX; index++) { char c = keytable[elevator][index]; if (c == '\0') { break; } if (c == (char)(keyCode & 0xFF)) { return index + 1; } } return 0; } ================================================ FILE: src/game/elevator.h ================================================ #ifndef FALLOUT_GAME_ELEVATOR_H_ #define FALLOUT_GAME_ELEVATOR_H_ typedef enum Elevator { ELEVATOR_BROTHERHOOD_OF_STEEL_MAIN, ELEVATOR_BROTHERHOOD_OF_STEEL_SURFACE, ELEVATOR_MASTER_UPPER, ELEVATOR_MASTER_LOWER, ELEVATOR_MILITARY_BASE_UPPER, ELEVATOR_MILITARY_BASE_LOWER, ELEVATOR_GLOW_UPPER, ELEVATOR_GLOW_LOWER, ELEVATOR_VAULT_13, ELEVATOR_NECROPOLIS, ELEVATOR_SIERRA_1, ELEVATOR_SIERRA_2, ELEVATOR_SIERRA_SERVICE, ELEVATOR_KLAMATH_TOXIC_CAVES, ELEVATOR_14, ELEVATOR_VAULT_CITY, ELEVATOR_VAULT_15_MAIN, ELEVATOR_VAULT_15_SURFACE, ELEVATOR_NAVARRO_NORTHERN, ELEVATOR_NAVARRO_CENTER, ELEVATOR_NAVARRO_LAB, ELEVATOR_NAVARRO_CANTEEN, ELEVATOR_SAN_FRANCISCO_SHI_TEMPLE, ELEVATOR_REDDING_WANAMINGO_MINE, ELEVATOR_COUNT, } Elevator; int elevator_select(int elevator, int* mapPtr, int* elevationPtr, int* tilePtr); #endif /* FALLOUT_GAME_ELEVATOR_H_ */ ================================================ FILE: src/game/endgame.c ================================================ #include "game/endgame.h" #include #include #include #include #include #include #define WIN32_LEAN_AND_MEAN #include #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/credits.h" #include "game/cycle.h" #include "plib/db/db.h" #include "game/bmpdlog.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/palette.h" #include "game/pipboy.h" #include "game/roll.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #include "game/wordwrap.h" #include "game/worldmap.h" // The maximum number of subtitle lines per slide. #define ENDGAME_ENDING_MAX_SUBTITLES 50 #define ENDGAME_ENDING_WINDOW_WIDTH 640 #define ENDGAME_ENDING_WINDOW_HEIGHT 480 typedef struct EndgameDeathEnding { int gvar; int value; int worldAreaKnown; int worldAreaNotKnown; int min_level; int percentage; char voiceOverBaseName[16]; // This flag denotes that the conditions for this ending is met and it was // selected as a candidate for final random selection. bool enabled; } EndgameDeathEnding; typedef struct EndgameEnding { int gvar; int value; int art_num; char voiceOverBaseName[12]; int direction; } EndgameEnding; static void endgame_pan_desert(int direction, const char* narratorFileName); static void endgame_display_image(int fid, const char* narratorFileName); static int endgame_init(); static void endgame_exit(); static void endgame_load_voiceover(const char* fname); static void endgame_play_voiceover(); static void endgame_stop_voiceover(); static void endgame_load_palette(int type, int id); static void endgame_voiceover_callback(); static int endgame_load_subtitles(const char* filePath); static void endgame_show_subtitles(); static void endgame_clear_subtitles(); static void endgame_movie_callback(); static void endgame_movie_bk_process(); static int endgame_load_slide_info(); static void endgame_unload_slide_info(); static int endgameSetupInit(int* percentage); // TODO: Remove. // 0x50B00C char _aEnglish_2[] = ENGLISH; // The number of lines in current subtitles file. // // It's used as a length for two arrays: // - [endgame_subtitle_text] // - [endgame_subtitle_times] // // This value does not exceed [ENDGAME_ENDING_SUBTITLES_CAPACITY]. // // 0x518668 static int endgame_subtitle_count = 0; // The number of characters in current subtitles file. // // This value is used to determine // // 0x51866C static int endgame_subtitle_characters = 0; // 0x518670 static int endgame_current_subtitle = 0; // 0x518674 static int endgame_maybe_done = 0; // enddeath.txt // // 0x518678 static EndgameDeathEnding* endDeathInfoList = NULL; // The number of death endings in [endDeathInfoList] array. // // 0x51867C static int maxEndDeathInfo = 0; // Base file name for death ending. // // This value does not include extension. // // 0x570A90 static char endDeathSndChoice[40]; // This flag denotes whether speech sound was successfully loaded for // the current slide. // // 0x570AB8 static bool endgame_voiceover_loaded; // 0x570ABC static char endgame_subtitle_path[MAX_PATH]; // The flag used to denote voice over speech for current slide has ended. // // 0x570BC0 static bool endgame_voiceover_done; // endgame.txt // // 0x570BC4 static EndgameEnding* slides; // The array of text lines in current subtitles file. // // The length is specified in [endgame_subtitle_count]. It's capacity // is [ENDGAME_ENDING_SUBTITLES_CAPACITY]. // // 0x570BC8 static char** endgame_subtitle_text; // 0x570BCC static bool endgame_do_subtitles; // The flag used to denote voice over subtitles for current slide has ended. // // 0x570BD0 static bool endgame_subtitle_done; // 0x570BD4 static bool endgame_map_enabled; // 0x570BD8 static bool endgame_mouse_state; // The number of endings in [slides] array. // // 0x570BDC static int num_slides = 0; // This flag denotes whether subtitles was successfully loaded for // the current slide. // // 0x570BE0 static bool endgame_subtitle_loaded; // Reference time is a timestamp when subtitle is first displayed. // // This value is used together with [endgame_subtitle_times] array to // determine when next line needs to be displayed. // // 0x570BE4 static unsigned int endgame_subtitle_start_time; // The array of timings for each line in current subtitles file. // // The length is specified in [endgame_subtitle_count]. It's capacity // is [ENDGAME_ENDING_SUBTITLES_CAPACITY]. // // 0x570BE8 static unsigned int* endgame_subtitle_times; // Font that was current before endgame slideshow window was created. // // 0x570BEC static int endgame_old_font; // 0x570BF0 static unsigned char* endgame_window_buffer; // 0x570BF4 static int endgame_window; // 0x43F788 void endgame_slideshow() { if (endgame_init() == -1) { return; } for (int index = 0; index < num_slides; index++) { EndgameEnding* ending = &(slides[index]); int value = game_get_global_var(ending->gvar); if (value == ending->value) { if (ending->art_num == 327) { endgame_pan_desert(ending->direction, ending->voiceOverBaseName); } else { int fid = art_id(OBJ_TYPE_INTERFACE, ending->art_num, 0, 0, 0); endgame_display_image(fid, ending->voiceOverBaseName); } } } endgame_exit(); } // 0x43F810 void endgame_movie() { gsound_background_stop(); map_disable_bk_processes(); palette_fade_to(black_palette); endgame_maybe_done = 0; add_bk_process(endgame_movie_bk_process); gsound_background_callback_set(endgame_movie_callback); gsound_background_play("akiss", 12, 14, 15); pause_for_tocks(3000); // NOTE: Result is ignored. I guess there was some kind of switch for male // vs. female ending, but it was not implemented. critterGetStat(obj_dude, STAT_GENDER); credits("credits.txt", -1, false); gsound_background_stop(); gsound_background_callback_set(NULL); remove_bk_process(endgame_movie_bk_process); gsound_background_stop(); loadColorTable("color.pal"); palette_fade_to(cmap); map_enable_bk_processes(); endgameEndingHandleContinuePlaying(); } // 0x43F8C4 int endgameEndingHandleContinuePlaying() { bool isoWasEnabled = map_disable_bk_processes(); bool gameMouseWasVisible; if (isoWasEnabled) { gameMouseWasVisible = gmouse_3d_is_on(); } else { gameMouseWasVisible = false; } if (gameMouseWasVisible) { gmouse_3d_off(); } bool oldCursorIsHidden = mouse_hidden(); if (oldCursorIsHidden) { mouse_show(); } int oldCursor = gmouse_get_cursor(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); int rc; MessageListItem messageListItem; messageListItem.num = 30; if (message_search(&misc_message_file, &messageListItem)) { rc = dialog_out(messageListItem.text, NULL, 0, 169, 117, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO); if (rc == 0) { game_user_wants_to_quit = 2; } } else { rc = -1; } gmouse_set_cursor(oldCursor); if (oldCursorIsHidden) { mouse_hide(); } if (gameMouseWasVisible) { gmouse_3d_on(); } if (isoWasEnabled) { map_enable_bk_processes(); } return rc; } // 0x43FBDC static void endgame_pan_desert(int direction, const char* narratorFileName) { int fid = art_id(OBJ_TYPE_INTERFACE, 327, 0, 0, 0); CacheEntry* backgroundHandle; Art* background = art_ptr_lock(fid, &backgroundHandle); if (background != NULL) { int width = art_frame_width(background, 0, 0); int height = art_frame_length(background, 0, 0); unsigned char* backgroundData = art_frame_data(background, 0, 0); buf_fill(endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, colorTable[0]); endgame_load_palette(6, 327); unsigned char palette[768]; memcpy(palette, cmap, 768); palette_set_to(black_palette); endgame_load_voiceover(narratorFileName); // TODO: Unclear math. int v8 = width - 640; int v32 = v8 / 4; unsigned int v9 = 16 * v8 / v8; unsigned int v9_ = 16 * v8; if (endgame_voiceover_loaded) { unsigned int v10 = 1000 * gsound_speech_length_get(); if (v10 > v9_ / 2) { v9 = (v10 + v9 * (v8 / 2)) / v8; } } int start; int end; if (direction == -1) { start = width - 640; end = 0; } else { start = 0; end = width - 640; } disable_bk(); bool subtitlesLoaded = false; unsigned int since = 0; while (start != end) { int v12 = 640 - v32; // TODO: Complex math, setup scene in debugger. if (elapsed_time(since) >= v9) { buf_to_buf(backgroundData + start, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, width, endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH); if (subtitlesLoaded) { endgame_show_subtitles(); } win_draw(endgame_window); since = get_time(); bool v14; double v31; if (start > v32) { if (v12 > start) { v14 = false; } else { int v28 = v32 - (start - v12); v31 = (double)v28 / (double)v32; v14 = true; } } else { v14 = true; v31 = (double)start / (double)v32; } if (v14) { unsigned char darkenedPalette[768]; for (int index = 0; index < 768; index++) { darkenedPalette[index] = (unsigned char)trunc(palette[index] * v31); } palette_set_to(darkenedPalette); } start += direction; if (direction == 1 && (start == v32)) { // NOTE: Uninline. endgame_play_voiceover(); subtitlesLoaded = true; } else if (direction == -1 && (start == v12)) { // NOTE: Uninline. endgame_play_voiceover(); subtitlesLoaded = true; } } soundContinueAll(); if (get_input() != -1) { // NOTE: Uninline. endgame_stop_voiceover(); break; } } enable_bk(); art_ptr_unlock(backgroundHandle); palette_fade_to(black_palette); buf_fill(endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, colorTable[0]); win_draw(endgame_window); } while (mouse_get_buttons() != 0) { get_input(); } } // 0x440004 static void endgame_display_image(int fid, const char* narratorFileName) { CacheEntry* backgroundHandle; Art* background = art_ptr_lock(fid, &backgroundHandle); if (background == NULL) { return; } unsigned char* backgroundData = art_frame_data(background, 0, 0); if (backgroundData != NULL) { buf_to_buf(backgroundData, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH); win_draw(endgame_window); endgame_load_palette(FID_TYPE(fid), fid & 0xFFF); endgame_load_voiceover(narratorFileName); unsigned int delay; if (endgame_subtitle_loaded || endgame_voiceover_loaded) { delay = UINT_MAX; } else { delay = 3000; } palette_fade_to(cmap); pause_for_tocks(500); // NOTE: Uninline. endgame_play_voiceover(); unsigned int referenceTime = get_time(); disable_bk(); int keyCode; while (true) { keyCode = get_input(); if (keyCode != -1) { break; } if (endgame_voiceover_done) { break; } if (endgame_subtitle_done) { break; } if (elapsed_time(referenceTime) > delay) { break; } buf_to_buf(backgroundData, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, ENDGAME_ENDING_WINDOW_WIDTH, endgame_window_buffer, ENDGAME_ENDING_WINDOW_WIDTH); endgame_show_subtitles(); win_draw(endgame_window); soundContinueAll(); } enable_bk(); gsound_speech_stop(); endgame_clear_subtitles(); endgame_voiceover_loaded = false; endgame_subtitle_loaded = false; if (keyCode == -1) { pause_for_tocks(500); } palette_fade_to(black_palette); while (mouse_get_buttons() != 0) { get_input(); } } art_ptr_unlock(backgroundHandle); } // 0x43F99C static int endgame_init() { if (endgame_load_slide_info() != 0) { return -1; } gsound_background_stop(); endgame_map_enabled = map_disable_bk_processes(); cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_NONE); bool oldCursorIsHidden = mouse_hidden(); endgame_mouse_state = oldCursorIsHidden == 0; if (oldCursorIsHidden) { mouse_show(); } endgame_old_font = text_curr(); text_font(101); palette_fade_to(black_palette); int windowEndgameEndingX = 0; int windowEndgameEndingY = 0; endgame_window = win_add(windowEndgameEndingX, windowEndgameEndingY, ENDGAME_ENDING_WINDOW_WIDTH, ENDGAME_ENDING_WINDOW_HEIGHT, colorTable[0], WINDOW_FLAG_0x04); if (endgame_window == -1) { return -1; } endgame_window_buffer = win_get_buf(endgame_window); if (endgame_window_buffer == NULL) { return -1; } cycle_disable(); gsound_speech_callback_set(endgame_voiceover_callback); endgame_do_subtitles = false; configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &endgame_do_subtitles); if (!endgame_do_subtitles) { return 0; } char* language; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { endgame_do_subtitles = false; return 0; } sprintf(endgame_subtitle_path, "text\\%s\\cuts\\", language); endgame_subtitle_text = (char**)mem_malloc(sizeof(*endgame_subtitle_text) * ENDGAME_ENDING_MAX_SUBTITLES); if (endgame_subtitle_text == NULL) { endgame_do_subtitles = false; return 0; } for (int index = 0; index < ENDGAME_ENDING_MAX_SUBTITLES; index++) { endgame_subtitle_text[index] = NULL; } endgame_subtitle_times = (unsigned int*)mem_malloc(sizeof(*endgame_subtitle_times) * ENDGAME_ENDING_MAX_SUBTITLES); if (endgame_subtitle_times == NULL) { mem_free(endgame_subtitle_text); endgame_do_subtitles = false; return 0; } return 0; } // 0x43FB28 static void endgame_exit() { if (endgame_do_subtitles) { endgame_clear_subtitles(); mem_free(endgame_subtitle_times); mem_free(endgame_subtitle_text); endgame_subtitle_text = NULL; endgame_do_subtitles = false; } // NOTE: Uninline. endgame_unload_slide_info(); text_font(endgame_old_font); gsound_speech_callback_set(NULL); win_delete(endgame_window); if (!endgame_mouse_state) { mouse_hide(); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); loadColorTable("color.pal"); palette_fade_to(cmap); cycle_enable(); if (endgame_map_enabled) { map_enable_bk_processes(); } } // 0x4401A0 static void endgame_load_voiceover(const char* fileBaseName) { char path[MAX_PATH]; // NOTE: Uninline. endgame_stop_voiceover(); endgame_voiceover_loaded = false; endgame_subtitle_loaded = false; // Build speech file path. sprintf(path, "%s%s", "narrator\\", fileBaseName); if (gsound_speech_play(path, 10, 14, 15) != -1) { endgame_voiceover_loaded = true; } if (endgame_do_subtitles) { // Build subtitles file path. sprintf(path, "%s%s.txt", endgame_subtitle_path, fileBaseName); if (endgame_load_subtitles(path) != 0) { return; } double durationPerCharacter; if (endgame_voiceover_loaded) { durationPerCharacter = (double)gsound_speech_length_get() / (double)endgame_subtitle_characters; } else { durationPerCharacter = 0.08; } unsigned int timing = 0; for (int index = 0; index < endgame_subtitle_count; index++) { double charactersCount = strlen(endgame_subtitle_text[index]); // NOTE: There is floating point math at 0x4402E6 used to add // timing. timing += (unsigned int)trunc(charactersCount * durationPerCharacter * 1000.0); endgame_subtitle_times[index] = timing; } endgame_subtitle_loaded = true; } } // NOTE: This function was inlined at every call site. // // 0x440324 static void endgame_play_voiceover() { endgame_subtitle_done = false; endgame_voiceover_done = false; if (endgame_voiceover_loaded) { gsound_speech_play_preloaded(); } if (endgame_subtitle_loaded) { endgame_subtitle_start_time = get_time(); } } // NOTE: This function was inlined at every call site. // // 0x44035C static void endgame_stop_voiceover() { gsound_speech_stop(); endgame_clear_subtitles(); endgame_voiceover_loaded = false; endgame_subtitle_loaded = false; } // 0x440378 static void endgame_load_palette(int type, int id) { char fileName[13]; if (art_get_base_name(type, id, fileName) != 0) { return; } // Remove extension from file name. char* pch = strrchr(fileName, '.'); if (pch != NULL) { *pch = '\0'; } if (strlen(fileName) <= 8) { char path[MAX_PATH]; sprintf(path, "%s\\%s.pal", "art\\intrface", fileName); loadColorTable(path); } } // 0x4403F0 static void endgame_voiceover_callback() { endgame_voiceover_done = true; } // Loads subtitles file. // // 0x4403FC static int endgame_load_subtitles(const char* filePath) { endgame_clear_subtitles(); File* stream = db_fopen(filePath, "rt"); if (stream == NULL) { return -1; } // FIXME: There is at least one subtitle for Arroyo ending (nar_ar1) that // does not fit into this buffer. char string[256]; while (db_fgets(string, sizeof(string), stream)) { char* pch; // Find and clamp string at EOL. pch = strchr(string, '\n'); if (pch != NULL) { *pch = '\0'; } // Find separator. The value before separator is ignored (as opposed to // movie subtitles, where the value before separator is a timing). pch = strchr(string, ':'); if (pch != NULL) { if (endgame_subtitle_count < ENDGAME_ENDING_MAX_SUBTITLES) { endgame_subtitle_text[endgame_subtitle_count] = mem_strdup(pch + 1); endgame_subtitle_count++; endgame_subtitle_characters += strlen(pch + 1); } } } db_fclose(stream); return 0; } // Refreshes subtitles. // // 0x4404EC static void endgame_show_subtitles() { if (endgame_subtitle_count <= endgame_current_subtitle) { if (endgame_subtitle_loaded) { endgame_subtitle_done = true; } return; } if (elapsed_time(endgame_subtitle_start_time) > endgame_subtitle_times[endgame_current_subtitle]) { endgame_current_subtitle++; return; } char* text = endgame_subtitle_text[endgame_current_subtitle]; if (text == NULL) { return; } short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (word_wrap(text, 540, beginnings, &count) != 0) { return; } int height = text_height(); int y = 480 - height * count; for (int index = 0; index < count - 1; index++) { char* beginning = text + beginnings[index]; char* ending = text + beginnings[index + 1]; if (ending[-1] == ' ') { ending--; } char c = *ending; *ending = '\0'; int width = text_width(beginning); int x = (640 - width) / 2; buf_fill(endgame_window_buffer + 640 * y + x, width, height, 640, colorTable[0]); text_to_buf(endgame_window_buffer + 640 * y + x, beginning, width, 640, colorTable[32767]); *ending = c; y += height; } } // 0x4406CC static void endgame_clear_subtitles() { for (int index = 0; index < endgame_subtitle_count; index++) { if (endgame_subtitle_text[index] != NULL) { mem_free(endgame_subtitle_text[index]); endgame_subtitle_text[index] = NULL; } } endgame_current_subtitle = 0; endgame_subtitle_characters = 0; endgame_subtitle_count = 0; } // 0x440728 static void endgame_movie_callback() { endgame_maybe_done = 1; } // 0x440734 static void endgame_movie_bk_process() { if (endgame_maybe_done) { gsound_background_play("10labone", 11, 14, 16); gsound_background_callback_set(NULL); remove_bk_process(endgame_movie_bk_process); } } // 0x440770 static int endgame_load_slide_info() { File* stream; char str[256]; char *ch, *tok; const char* delim = " \t,"; EndgameEnding entry; EndgameEnding* entries; int narrator_file_len; if (slides != NULL) { mem_free(slides); slides = NULL; } num_slides = 0; stream = db_fopen("data\\endgame.txt", "rt"); if (stream == NULL) { return -1; } while (db_fgets(str, sizeof(str), stream)) { ch = str; while (isspace(*ch)) { ch++; } if (*ch == '#') { continue; } tok = strtok(ch, delim); if (tok == NULL) { continue; } entry.gvar = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.value = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.art_num = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } strcpy(entry.voiceOverBaseName, tok); narrator_file_len = strlen(entry.voiceOverBaseName); if (isspace(entry.voiceOverBaseName[narrator_file_len - 1])) { entry.voiceOverBaseName[narrator_file_len - 1] = '\0'; } tok = strtok(NULL, delim); if (tok != NULL) { entry.direction = atoi(tok); } else { entry.direction = 1; } entries = (EndgameEnding*)mem_realloc(slides, sizeof(*entries) * (num_slides + 1)); if (entries == NULL) { goto err; } memcpy(&(entries[num_slides]), &entry, sizeof(entry)); slides = entries; num_slides++; } db_fclose(stream); return 0; err: db_fclose(stream); return -1; } // NOTE: There are no references to this function. It was inlined. // // 0x44095C static void endgame_unload_slide_info() { if (slides != NULL) { mem_free(slides); slides = NULL; } num_slides = 0; } // endgameDeathEndingInit // 0x440984 int endgameDeathEndingInit() { File* stream; char str[256]; char* ch; const char* delim = " \t,"; char* tok; EndgameDeathEnding entry; EndgameDeathEnding* entries; int narrator_file_len; strcpy(endDeathSndChoice, "narrator\\nar_5"); stream = db_fopen("data\\enddeath.txt", "rt"); if (stream == NULL) { return -1; } while (db_fgets(str, 256, stream)) { ch = str; while (isspace(*ch)) { ch++; } if (*ch == '#') { continue; } tok = strtok(ch, delim); if (tok == NULL) { continue; } entry.gvar = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.value = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.worldAreaKnown = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.worldAreaNotKnown = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.min_level = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.percentage = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } // this code is slightly different from the original, but does the same thing narrator_file_len = strlen(tok); strncpy(entry.voiceOverBaseName, tok, narrator_file_len); entry.enabled = false; if (isspace(entry.voiceOverBaseName[narrator_file_len - 1])) { entry.voiceOverBaseName[narrator_file_len - 1] = '\0'; } entries = (EndgameDeathEnding*)mem_realloc(endDeathInfoList, sizeof(*entries) * (maxEndDeathInfo + 1)); if (entries == NULL) { goto err; } memcpy(&(entries[maxEndDeathInfo]), &entry, sizeof(entry)); endDeathInfoList = entries; maxEndDeathInfo++; } db_fclose(stream); return 0; err: db_fclose(stream); return -1; } // 0x440BA8 int endgameDeathEndingExit() { if (endDeathInfoList != NULL) { mem_free(endDeathInfoList); endDeathInfoList = NULL; maxEndDeathInfo = 0; } return 0; } // endgameSetupDeathEnding // 0x440BD0 void endgameSetupDeathEnding(int reason) { if (!maxEndDeathInfo) { debug_printf("\nError: endgameSetupDeathEnding: No endgame death info!"); return; } // Build death ending file path. strcpy(endDeathSndChoice, "narrator\\"); int percentage = 0; endgameSetupInit(&percentage); int selectedEnding = 0; bool specialEndingSelected = false; switch (reason) { case ENDGAME_DEATH_ENDING_REASON_DEATH: if (game_get_global_var(GVAR_MODOC_SHITTY_DEATH) != 0) { selectedEnding = 12; specialEndingSelected = true; } break; case ENDGAME_DEATH_ENDING_REASON_TIMEOUT: gmovie_play(MOVIE_TIMEOUT, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC); break; } if (!specialEndingSelected) { int chance = roll_random(0, percentage); int accum = 0; for (int index = 0; index < maxEndDeathInfo; index++) { EndgameDeathEnding* deathEnding = &(endDeathInfoList[index]); if (deathEnding->enabled) { accum += deathEnding->percentage; if (accum >= chance) { break; } selectedEnding++; } } } EndgameDeathEnding* deathEnding = &(endDeathInfoList[selectedEnding]); strcat(endDeathSndChoice, deathEnding->voiceOverBaseName); debug_printf("\nendgameSetupDeathEnding: Death Filename Picked: %s", endDeathSndChoice); } // Validates conditions imposed by death endings. // // Upon return [percentage] is set as a sum of all valid endings' percentages. // Always returns 0. // // 0x440CF4 static int endgameSetupInit(int* percentage) { *percentage = 0; for (int index = 0; index < maxEndDeathInfo; index++) { EndgameDeathEnding* deathEnding = &(endDeathInfoList[index]); deathEnding->enabled = false; if (deathEnding->gvar != -1) { if (game_get_global_var(deathEnding->gvar) >= deathEnding->value) { continue; } } if (deathEnding->worldAreaKnown != -1) { if (!wmAreaIsKnown(deathEnding->worldAreaKnown)) { continue; } } if (deathEnding->worldAreaNotKnown != -1) { if (wmAreaIsKnown(deathEnding->worldAreaNotKnown)) { continue; } } if (stat_pc_get(PC_STAT_LEVEL) < deathEnding->min_level) { continue; } deathEnding->enabled = true; *percentage += deathEnding->percentage; } return 0; } // Returns file name for voice over for death ending. // // This path does not include extension. // // 0x440D8C char* endgameGetDeathEndingFileName() { if (maxEndDeathInfo == 0) { debug_printf("\nError: endgameSetupDeathEnding: No endgame death info!"); strcpy(endDeathSndChoice, "narrator\\nar_4"); } debug_printf("\nendgameSetupDeathEnding: Death Filename: %s", endDeathSndChoice); return endDeathSndChoice; } ================================================ FILE: src/game/endgame.h ================================================ #ifndef FALLOUT_GAME_ENDGAME_H_ #define FALLOUT_GAME_ENDGAME_H_ typedef enum EndgameDeathEndingReason { // Dude died. ENDGAME_DEATH_ENDING_REASON_DEATH = 0, // 13 years passed. ENDGAME_DEATH_ENDING_REASON_TIMEOUT = 2, } EndgameDeathEndingReason; extern char _aEnglish_2[]; void endgame_slideshow(); void endgame_movie(); int endgameEndingHandleContinuePlaying(); int endgameDeathEndingInit(); int endgameDeathEndingExit(); void endgameSetupDeathEnding(int reason); char* endgameGetDeathEndingFileName(); #endif /* FALLOUT_GAME_ENDGAME_H_ */ ================================================ FILE: src/game/ereg.c ================================================ #include "game/ereg.h" #define WIN32_LEAN_AND_MEAN #include #include "game/gconfig.h" // 0x440DD0 void annoy_user() { int timesRun = 0; config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, ×Run); if (timesRun > 0 && timesRun < 5) { char path[MAX_PATH]; if (GetModuleFileNameA(NULL, path, sizeof(path)) != 0) { char* pch = strrchr(path, '\\'); if (pch == NULL) { pch = path; } strcpy(pch, "\\ereg"); STARTUPINFOA startupInfo; memset(&startupInfo, 0, sizeof(startupInfo)); startupInfo.cb = sizeof(startupInfo); PROCESS_INFORMATION processInfo; // FIXME: Leaking processInfo.hProcess and processInfo.hThread: // https://docs.microsoft.com/en-us/cpp/code-quality/c6335. if (CreateProcessA("ereg\\reg32a.exe", NULL, NULL, NULL, FALSE, 0, NULL, path, &startupInfo, &processInfo)) { WaitForSingleObject(processInfo.hProcess, INFINITE); } } config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, timesRun + 1); } else { if (timesRun == 0) { config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_TIMES_RUN_KEY, 1); } } } ================================================ FILE: src/game/ereg.h ================================================ #ifndef FALLOUT_GAME_EREG_H_ #define FALLOUT_GAME_EREG_H_ void annoy_user(); #endif /* FALLOUT_GAME_EREG_H_ */ ================================================ FILE: src/game/fontmgr.c ================================================ #include "game/fontmgr.h" #include #include #include #include "plib/color/color.h" #include "plib/db/db.h" #include "int/memdbg.h" #include "plib/gnw/text.h" // The maximum number of interface fonts. #define INTERFACE_FONT_MAX 16 typedef struct InterfaceFontGlyph { short width; short height; int offset; } InterfaceFontGlyph; typedef struct InterfaceFontDescriptor { short maxHeight; short letterSpacing; short wordSpacing; short lineSpacing; short field_8; short field_A; InterfaceFontGlyph glyphs[256]; unsigned char* data; } InterfaceFontDescriptor; static int FMLoadFont(int font); static void Swap4(unsigned int* value); static void Swap2(unsigned short* value); // 0x518680 static bool gFMInit = false; // 0x518684 static int gNumFonts = 0; // 0x586838 static InterfaceFontDescriptor gFontCache[INTERFACE_FONT_MAX]; // 0x58E938 static int gCurrentFontNum; // 0x58E93C static InterfaceFontDescriptor* gCurrentFont; // 0x441C80 int FMInit() { int currentFont = -1; for (int font = 0; font < INTERFACE_FONT_MAX; font++) { if (FMLoadFont(font) == -1) { gFontCache[font].maxHeight = 0; gFontCache[font].data = NULL; } else { ++gNumFonts; if (currentFont == -1) { currentFont = font; } } } if (currentFont == -1) { return -1; } gFMInit = true; FMtext_font(currentFont + 100); return 0; } // 0x441CEC void FMExit() { for (int font = 0; font < INTERFACE_FONT_MAX; font++) { if (gFontCache[font].data != NULL) { myfree(gFontCache[font].data, __FILE__, __LINE__); // FONTMGR.C, 124 } } } // 0x441D20 static int FMLoadFont(int font_index) { InterfaceFontDescriptor* fontDescriptor = &(gFontCache[font_index]); char path[56]; sprintf(path, "font%d.aaf", font_index); File* stream = db_fopen(path, "rb"); if (stream == NULL) { return -1; } int fileSize = db_filelength(stream); int sig; if (db_fread(&sig, 4, 1, stream) != 1) { db_fclose(stream); return -1; } Swap4(&sig); if (sig != 0x41414646) { db_fclose(stream); return -1; } if (db_fread(&(fontDescriptor->maxHeight), 2, 1, stream) != 1) { db_fclose(stream); return -1; } Swap2(&(fontDescriptor->maxHeight)); if (db_fread(&(fontDescriptor->letterSpacing), 2, 1, stream) != 1) { db_fclose(stream); return -1; } Swap2(&(fontDescriptor->letterSpacing)); if (db_fread(&(fontDescriptor->wordSpacing), 2, 1, stream) != 1) { db_fclose(stream); return -1; } Swap2(&(fontDescriptor->wordSpacing)); if (db_fread(&(fontDescriptor->lineSpacing), 2, 1, stream) != 1) { db_fclose(stream); return -1; } Swap2(&(fontDescriptor->lineSpacing)); for (int index = 0; index < 256; index++) { InterfaceFontGlyph* glyph = &(fontDescriptor->glyphs[index]); if (db_fread(&(glyph->width), 2, 1, stream) != 1) { db_fclose(stream); return -1; } Swap2(&(glyph->width)); if (db_fread(&(glyph->height), 2, 1, stream) != 1) { db_fclose(stream); return -1; } Swap2(&(glyph->height)); if (db_fread(&(glyph->offset), 4, 1, stream) != 1) { db_fclose(stream); return -1; } Swap4(&(glyph->offset)); } int glyphDataSize = fileSize - 2060; fontDescriptor->data = (unsigned char*)mymalloc(glyphDataSize, __FILE__, __LINE__); // FONTMGR.C, 259 if (fontDescriptor->data == NULL) { db_fclose(stream); return -1; } if (db_fread(fontDescriptor->data, glyphDataSize, 1, stream) != 1) { myfree(fontDescriptor->data, __FILE__, __LINE__); // FONTMGR.C, 268 db_fclose(stream); return -1; } db_fclose(stream); return 0; } // 0x442120 void FMtext_font(int font) { if (!gFMInit) { return; } font -= 100; if (gFontCache[font].data != NULL) { gCurrentFontNum = font; gCurrentFont = &(gFontCache[font]); } } // 0x442168 int FMtext_height() { if (!gFMInit) { return 0; } return gCurrentFont->lineSpacing + gCurrentFont->maxHeight; } // 0x442188 int FMtext_width(const char* string) { if (!gFMInit) { return 0; } int stringWidth = 0; while (*string != '\0') { unsigned char ch = (unsigned char)(*string++); int characterWidth; if (ch == ' ') { characterWidth = gCurrentFont->wordSpacing; } else { characterWidth = gCurrentFont->glyphs[ch].width; } stringWidth += characterWidth + gCurrentFont->letterSpacing; } return stringWidth; } // 0x4421DC int FMtext_char_width(int ch) { int width; if (!gFMInit) { return 0; } if (ch == ' ') { width = gCurrentFont->wordSpacing; } else { width = gCurrentFont->glyphs[ch].width; } return width; } // 0x442210 int FMtext_mono_width(const char* str) { if (!gFMInit) { return 0; } return FMtext_max() * strlen(str); } // 0x442240 int FMtext_spacing() { if (!gFMInit) { return 0; } return gCurrentFont->letterSpacing; } // 0x442258 int FMtext_size(const char* str) { if (!gFMInit) { return 0; } return FMtext_width(str) * FMtext_height(); } // 0x442278 int FMtext_max() { if (!gFMInit) { return 0; } int v1; if (gCurrentFont->wordSpacing <= gCurrentFont->field_8) { v1 = gCurrentFont->lineSpacing; } else { v1 = gCurrentFont->letterSpacing; } return v1 + gCurrentFont->maxHeight; } // NOTE: Unused. // // 0x4422AC int FMtext_curr() { return gCurrentFontNum; } // 0x4422B4 void FMtext_to_buf(unsigned char* buf, const char* string, int length, int pitch, int color) { if (!gFMInit) { return; } if ((color & FONT_SHADOW) != 0) { color &= ~FONT_SHADOW; // NOTE: Other font options preserved. This is different from text font // shadows. FMtext_to_buf(buf + pitch + 1, string, length, pitch, (color & ~0xFF) | colorTable[0]); } unsigned char* palette = getColorBlendTable(color & 0xFF); int monospacedCharacterWidth; if ((color & FONT_MONO) != 0) { // NOTE: Uninline. monospacedCharacterWidth = FMtext_max(); } unsigned char* ptr = buf; while (*string != '\0') { char ch = *string++; int characterWidth; if (ch == ' ') { characterWidth = gCurrentFont->wordSpacing; } else { characterWidth = gCurrentFont->glyphs[ch & 0xFF].width; } unsigned char* end; if ((color & FONT_MONO) != 0) { end = ptr + monospacedCharacterWidth; ptr += (monospacedCharacterWidth - characterWidth - gCurrentFont->letterSpacing) / 2; } else { end = ptr + characterWidth + gCurrentFont->letterSpacing; } if (end - buf > length) { break; } InterfaceFontGlyph* glyph = &(gCurrentFont->glyphs[ch & 0xFF]); unsigned char* glyphDataPtr = gCurrentFont->data + glyph->offset; // Skip blank pixels (difference between font's line height and glyph height). ptr += (gCurrentFont->maxHeight - glyph->height) * pitch; for (int y = 0; y < glyph->height; y++) { for (int x = 0; x < glyph->width; x++) { unsigned char byte = *glyphDataPtr++; *ptr++ = palette[(byte << 8) + *ptr]; } ptr += pitch - glyph->width; } ptr = end; } if ((color & FONT_UNDERLINE) != 0) { int length = ptr - buf; unsigned char* underlinePtr = buf + pitch * (gCurrentFont->maxHeight - 1); for (int index = 0; index < length; index++) { *underlinePtr++ = color & 0xFF; } } freeColorBlendTable(color & 0xFF); } // NOTE: Inlined. // // 0x442520 static void Swap4(unsigned int* value) { unsigned int swapped = *value; unsigned short high = swapped >> 16; // NOTE: Uninline. Swap2(&high); unsigned short low = swapped & 0xFFFF; // NOTE: Uninline. Swap2(&low); *value = (low << 16) | high; } // 0x442568 static void Swap2(unsigned short* value) { unsigned short swapped = *value; swapped = (swapped >> 8) | (swapped << 8); *value = swapped; } ================================================ FILE: src/game/fontmgr.h ================================================ #ifndef FALLOUT_GAME_FONTMGR_H_ #define FALLOUT_GAME_FONTMGR_H_ int FMInit(); void FMExit(); void FMtext_font(int font); int FMtext_height(); int FMtext_width(const char* string); int FMtext_char_width(int ch); int FMtext_mono_width(const char* string); int FMtext_spacing(); int FMtext_size(const char* string); int FMtext_max(); int FMtext_curr(); void FMtext_to_buf(unsigned char* buf, const char* string, int length, int pitch, int color); #endif /* FALLOUT_GAME_FONTMGR_H_ */ ================================================ FILE: src/game/game.c ================================================ #include "game/game.h" #include #include #include #include "int/window.h" #include "game/actions.h" #include "game/anim.h" #include "game/automap.h" #include "game/editor.h" #include "game/select.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "plib/db/db.h" #include "game/bmpdlog.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/ereg.h" #include "game/endgame.h" #include "game/fontmgr.h" #include "game/gconfig.h" #include "game/gdialog.h" #include "game/gmemory.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/intface.h" #include "game/inventry.h" #include "game/item.h" #include "game/loadsave.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "int/movie.h" #include "game/moviefx.h" #include "game/object.h" #include "game/options.h" #include "game/palette.h" #include "game/party.h" #include "game/perk.h" #include "game/pipboy.h" #include "game/proto.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/skilldex.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "game/trait.h" #include "game/trap.h" #include "game/version.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" #include "game/worldmap.h" #define HELP_SCREEN_WIDTH 640 #define HELP_SCREEN_HEIGHT 480 #define SPLASH_WIDTH 640 #define SPLASH_HEIGHT 480 #define SPLASH_COUNT 10 static void game_display_counter(double value); static int game_screendump(int width, int height, unsigned char* buffer, unsigned char* palette); static void game_unload_info(); static void game_help(); static int game_init_databases(); static void game_splash_screen(); // TODO: Remove. // 0x501C9C char _aGame_0[] = "game\\"; // TODO: Remove. // 0x5020B8 char _aDec11199816543[] = VERSION_BUILD_TIME; // 0x518688 static FontMgr alias_mgr = { 100, 110, FMtext_font, FMtext_to_buf, FMtext_height, FMtext_width, FMtext_char_width, FMtext_mono_width, FMtext_spacing, FMtext_size, FMtext_max, }; // 0x5186B4 static bool game_ui_disabled = false; // 0x5186B8 static int game_state_cur = GAME_STATE_0; // 0x5186BC static bool game_in_mapper = false; // 0x5186C0 int* game_global_vars = NULL; // 0x5186C4 int num_game_global_vars = 0; // 0x5186C8 const char* msg_path = _aGame_0; // 0x5186CC int game_user_wants_to_quit = 0; // misc.msg // // 0x58E940 MessageList misc_message_file; // master.dat loading result // // 0x58E948 int master_db_handle; // critter.dat loading result // // 0x58E94C int critter_db_handle; // 0x442580 int game_init(const char* windowTitle, bool isMapper, int font, int a4, int argc, char** argv) { char path[MAX_PATH]; if (gmemory_init() == -1) { return -1; } gconfig_init(isMapper, argc, argv); game_in_mapper = isMapper; if (game_init_databases() == -1) { gconfig_exit(false); return -1; } annoy_user(); win_set_minimized_title(windowTitle); initWindow(1, a4); palette_init(); char* language; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { if (stricmp(language, FRENCH) == 0) { kb_set_layout(KEYBOARD_LAYOUT_FRENCH); } else if (stricmp(language, GERMAN) == 0) { kb_set_layout(KEYBOARD_LAYOUT_GERMAN); } else if (stricmp(language, ITALIAN) == 0) { kb_set_layout(KEYBOARD_LAYOUT_ITALIAN); } else if (stricmp(language, SPANISH) == 0) { kb_set_layout(KEYBOARD_LAYOUT_SPANISH); } } if (!game_in_mapper) { game_splash_screen(); } trap_init(); FMInit(); text_add_manager(&alias_mgr); text_font(font); register_screendump(KEY_F12, game_screendump); register_pause(-1, NULL); tile_disable_refresh(); roll_init(); init_message(); skill_init(); stat_init(); if (partyMember_init() != 0) { debug_printf("Failed on partyMember_init\n"); return -1; } perk_init(); trait_init(); item_init(); queue_init(); critter_init(); combat_ai_init(); inven_reset_dude(); if (gsound_init() != 0) { debug_printf("Sound initialization failed.\n"); } debug_printf(">gsound_init\t"); initMovie(); debug_printf(">initMovie\t\t"); if (gmovie_init() != 0) { debug_printf("Failed on gmovie_init\n"); return -1; } debug_printf(">gmovie_init\t"); if (moviefx_init() != 0) { debug_printf("Failed on moviefx_init\n"); return -1; } debug_printf(">moviefx_init\t"); if (iso_init() != 0) { debug_printf("Failed on iso_init\n"); return -1; } debug_printf(">iso_init\t"); if (gmouse_init() != 0) { debug_printf("Failed on gmouse_init\n"); return -1; } debug_printf(">gmouse_init\t"); if (proto_init() != 0) { debug_printf("Failed on proto_init\n"); return -1; } debug_printf(">proto_init\t"); anim_init(); debug_printf(">anim_init\t"); if (scr_init() != 0) { debug_printf("Failed on scr_init\n"); return -1; } debug_printf(">scr_init\t"); if (game_load_info() != 0) { debug_printf("Failed on game_load_info\n"); return -1; } debug_printf(">game_load_info\t"); if (scr_game_init() != 0) { debug_printf("Failed on scr_game_init\n"); return -1; } debug_printf(">scr_game_init\t"); if (wmWorldMap_init() != 0) { debug_printf("Failed on wmWorldMap_init\n"); return -1; } debug_printf(">wmWorldMap_init\t"); CharEditInit(); debug_printf(">CharEditInit\t"); pip_init(); debug_printf(">pip_init\t\t"); InitLoadSave(); KillOldMaps(); debug_printf(">InitLoadSave\t"); if (gdialogInit() != 0) { debug_printf("Failed on gdialog_init\n"); return -1; } debug_printf(">gdialog_init\t"); if (combat_init() != 0) { debug_printf("Failed on combat_init\n"); return -1; } debug_printf(">combat_init\t"); if (automap_init() != 0) { debug_printf("Failed on automap_init\n"); return -1; } debug_printf(">automap_init\t"); if (!message_init(&misc_message_file)) { debug_printf("Failed on message_init\n"); return -1; } debug_printf(">message_init\t"); sprintf(path, "%s%s", msg_path, "misc.msg"); if (!message_load(&misc_message_file, path)) { debug_printf("Failed on message_load\n"); return -1; } debug_printf(">message_load\t"); if (scr_disable() != 0) { debug_printf("Failed on scr_disable\n"); return -1; } debug_printf(">scr_disable\t"); if (init_options_menu() != 0) { debug_printf("Failed on init_options_menu\n"); return -1; } debug_printf(">init_options_menu\n"); if (endgameDeathEndingInit() != 0) { debug_printf("Failed on endgameDeathEndingInit"); return -1; } debug_printf(">endgameDeathEndingInit\n"); return 0; } // 0x442B84 void game_reset() { tile_disable_refresh(); palette_reset(); roll_reset(); skill_reset(); stat_reset(); perk_reset(); trait_reset(); item_reset(); queue_reset(); anim_reset(); KillOldMaps(); critter_reset(); combat_ai_reset(); inven_reset_dude(); gsound_reset(); movieStop(); moviefx_reset(); gmovie_reset(); iso_reset(); gmouse_reset(); proto_reset(); scr_reset(); game_load_info(); scr_game_reset(); wmWorldMap_reset(); partyMember_reset(); CharEditInit(); pip_init(); ResetLoadSave(); gdialogReset(); combat_reset(); game_user_wants_to_quit = 0; automap_reset(); init_options_menu(); } // 0x442C34 void game_exit() { debug_printf("\nGame Exit\n"); tile_disable_refresh(); message_exit(&misc_message_file); combat_exit(); gdialogExit(); scr_game_exit(); // NOTE: Uninline. game_unload_info(); scr_exit(); anim_exit(); proto_exit(); gmouse_exit(); iso_exit(); moviefx_exit(); movieClose(); gsound_exit(); combat_ai_exit(); critter_exit(); item_exit(); queue_exit(); perk_exit(); stat_exit(); skill_exit(); trait_exit(); roll_exit(); exit_message(); automap_exit(); palette_exit(); wmWorldMap_exit(); partyMember_exit(); endgameDeathEndingExit(); FMExit(); trap_exit(); windowClose(); db_exit(); gconfig_exit(true); } // 0x442D44 int game_handle_input(int eventCode, bool isInCombatMode) { // NOTE: Uninline. if (game_state() == GAME_STATE_5) { gdialogSystemEnter(); } if (eventCode == -1) { return 0; } if (eventCode == -2) { int mouseState = mouse_get_buttons(); int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) { if (mouseX == scr_size.ulx || mouseX == scr_size.lrx || mouseY == scr_size.uly || mouseY == scr_size.lry) { gmouse_clicked_on_edge = true; } else { gmouse_clicked_on_edge = false; } } } else { if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { gmouse_clicked_on_edge = false; } } gmouse_handle_event(mouseX, mouseY, mouseState); return 0; } if (gmouse_is_scrolling()) { return 0; } switch (eventCode) { case -20: if (intface_is_enabled()) { intface_use_item(); } break; case -2: if (1) { int mouseEvent = mouse_get_buttons(); int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) { if (mouseX == scr_size.ulx || mouseX == scr_size.lrx || mouseY == scr_size.uly || mouseY == scr_size.lry) { gmouse_clicked_on_edge = true; } else { gmouse_clicked_on_edge = false; } } } else { if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { gmouse_clicked_on_edge = false; } } gmouse_handle_event(mouseX, mouseY, mouseEvent); } break; case KEY_CTRL_Q: case KEY_CTRL_X: case KEY_F10: gsound_play_sfx_file("ib1p1xx1"); game_quit_with_confirm(); break; case KEY_TAB: if (intface_is_enabled() && keys[DIK_LALT] == 0 && keys[DIK_RALT] == 0) { gsound_play_sfx_file("ib1p1xx1"); automap(true, false); } break; case KEY_CTRL_P: gsound_play_sfx_file("ib1p1xx1"); PauseWindow(false); break; case KEY_UPPERCASE_A: case KEY_LOWERCASE_A: if (intface_is_enabled()) { if (!isInCombatMode) { combat(NULL); } } break; case KEY_UPPERCASE_N: case KEY_LOWERCASE_N: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); intface_toggle_item_state(); } break; case KEY_UPPERCASE_M: case KEY_LOWERCASE_M: gmouse_3d_toggle_mode(); break; case KEY_UPPERCASE_B: case KEY_LOWERCASE_B: // change active hand if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); intface_toggle_items(true); } break; case KEY_UPPERCASE_C: case KEY_LOWERCASE_C: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); bool isoWasEnabled = map_disable_bk_processes(); editor_design(false); if (isoWasEnabled) { map_enable_bk_processes(); } } break; case KEY_UPPERCASE_I: case KEY_LOWERCASE_I: // open inventory if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); handle_inventory(); } break; case KEY_ESCAPE: case KEY_UPPERCASE_O: case KEY_LOWERCASE_O: // options if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); do_options(); } break; case KEY_UPPERCASE_P: case KEY_LOWERCASE_P: // pipboy if (intface_is_enabled()) { if (isInCombatMode) { gsound_play_sfx_file("iisxxxx1"); // Pipboy not available in combat! MessageListItem messageListItem; char title[128]; strcpy(title, getmsg(&misc_message_file, &messageListItem, 7)); dialog_out(title, NULL, 0, 192, 116, colorTable[32328], NULL, colorTable[32328], 0); } else { gsound_play_sfx_file("ib1p1xx1"); pipboy(false); } } break; case KEY_UPPERCASE_S: case KEY_LOWERCASE_S: // skilldex if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); int mode = -1; // NOTE: There is an `inc` for this value to build jump table which // is not needed. int rc = skilldex_select(); // Remap Skilldex result code to action. switch (rc) { case SKILLDEX_RC_ERROR: debug_printf("\n ** Error calling skilldex_select()! ** \n"); break; case SKILLDEX_RC_SNEAK: action_skill_use(SKILL_SNEAK); break; case SKILLDEX_RC_LOCKPICK: mode = GAME_MOUSE_MODE_USE_LOCKPICK; break; case SKILLDEX_RC_STEAL: mode = GAME_MOUSE_MODE_USE_STEAL; break; case SKILLDEX_RC_TRAPS: mode = GAME_MOUSE_MODE_USE_TRAPS; break; case SKILLDEX_RC_FIRST_AID: mode = GAME_MOUSE_MODE_USE_FIRST_AID; break; case SKILLDEX_RC_DOCTOR: mode = GAME_MOUSE_MODE_USE_DOCTOR; break; case SKILLDEX_RC_SCIENCE: mode = GAME_MOUSE_MODE_USE_SCIENCE; break; case SKILLDEX_RC_REPAIR: mode = GAME_MOUSE_MODE_USE_REPAIR; break; default: break; } if (mode != -1) { gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(mode); } } break; case KEY_UPPERCASE_Z: case KEY_LOWERCASE_Z: if (intface_is_enabled()) { if (isInCombatMode) { gsound_play_sfx_file("iisxxxx1"); // Pipboy not available in combat! MessageListItem messageListItem; char title[128]; strcpy(title, getmsg(&misc_message_file, &messageListItem, 7)); dialog_out(title, NULL, 0, 192, 116, colorTable[32328], NULL, colorTable[32328], 0); } else { gsound_play_sfx_file("ib1p1xx1"); pipboy(true); } } break; case KEY_HOME: if (obj_dude->elevation != map_elevation) { map_set_elevation(obj_dude->elevation); } if (game_in_mapper) { tile_set_center(obj_dude->tile, TILE_SET_CENTER_REFRESH_WINDOW); } else { tile_scroll_to(obj_dude->tile, 2); } break; case KEY_1: case KEY_EXCLAMATION: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); action_skill_use(SKILL_SNEAK); } break; case KEY_2: case KEY_AT: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_LOCKPICK); } break; case KEY_3: case KEY_NUMBER_SIGN: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_STEAL); } break; case KEY_4: case KEY_DOLLAR: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_TRAPS); } break; case KEY_5: case KEY_PERCENT: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_FIRST_AID); } break; case KEY_6: case KEY_CARET: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_DOCTOR); } break; case KEY_7: case KEY_AMPERSAND: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_SCIENCE); } break; case KEY_8: case KEY_ASTERISK: if (intface_is_enabled()) { gsound_play_sfx_file("ib1p1xx1"); gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_REPAIR); } break; case KEY_MINUS: case KEY_UNDERSCORE: DecGamma(); break; case KEY_EQUAL: case KEY_PLUS: IncGamma(); break; case KEY_COMMA: case KEY_LESS: if (register_begin(ANIMATION_REQUEST_RESERVED) == 0) { register_object_dec_rotation(obj_dude); register_end(); } break; case KEY_DOT: case KEY_GREATER: if (register_begin(ANIMATION_REQUEST_RESERVED) == 0) { register_object_inc_rotation(obj_dude); register_end(); } break; case KEY_SLASH: case KEY_QUESTION: if (1) { gsound_play_sfx_file("ib1p1xx1"); int month; int day; int year; game_time_date(&month, &day, &year); MessageList messageList; if (message_init(&messageList)) { char path[FILENAME_MAX]; sprintf(path, "%s%s", msg_path, "editor.msg"); if (message_load(&messageList, path)) { MessageListItem messageListItem; messageListItem.num = 500 + month - 1; if (message_search(&messageList, &messageListItem)) { char* time = game_time_hour_str(); char date[128]; sprintf(date, "%s: %d/%d %s", messageListItem.text, day, year, time); display_print(date); } } message_exit(&messageList); } } break; case KEY_F1: gsound_play_sfx_file("ib1p1xx1"); game_help(); break; case KEY_F2: gsound_set_master_volume(gsound_get_master_volume() - 2047); break; case KEY_F3: gsound_set_master_volume(gsound_get_master_volume() + 2047); break; case KEY_CTRL_S: case KEY_F4: gsound_play_sfx_file("ib1p1xx1"); if (SaveGame(1) == -1) { debug_printf("\n ** Error calling SaveGame()! **\n"); } break; case KEY_CTRL_L: case KEY_F5: gsound_play_sfx_file("ib1p1xx1"); if (LoadGame(LOAD_SAVE_MODE_NORMAL) == -1) { debug_printf("\n ** Error calling LoadGame()! **\n"); } break; case KEY_F6: if (1) { gsound_play_sfx_file("ib1p1xx1"); int rc = SaveGame(LOAD_SAVE_MODE_QUICK); if (rc == -1) { debug_printf("\n ** Error calling SaveGame()! **\n"); } else if (rc == 1) { MessageListItem messageListItem; // Quick save game successfully saved. char* msg = getmsg(&misc_message_file, &messageListItem, 5); display_print(msg); } } break; case KEY_F7: if (1) { gsound_play_sfx_file("ib1p1xx1"); int rc = LoadGame(LOAD_SAVE_MODE_QUICK); if (rc == -1) { debug_printf("\n ** Error calling LoadGame()! **\n"); } else if (rc == 1) { MessageListItem messageListItem; // Quick load game successfully loaded. char* msg = getmsg(&misc_message_file, &messageListItem, 4); display_print(msg); } } break; case KEY_CTRL_V: if (1) { gsound_play_sfx_file("ib1p1xx1"); char version[VERSION_MAX]; getverstr(version); display_print(version); display_print(_aDec11199816543); } break; case KEY_ARROW_LEFT: map_scroll(-1, 0); break; case KEY_ARROW_RIGHT: map_scroll(1, 0); break; case KEY_ARROW_UP: map_scroll(0, -1); break; case KEY_ARROW_DOWN: map_scroll(0, 1); break; } return 0; } // game_ui_disable // 0x443BFC void game_ui_disable(int a1) { if (!game_ui_disabled) { gmouse_3d_off(); gmouse_disable(a1); kb_disable(); intface_disable(); game_ui_disabled = true; } } // game_ui_enable // 0x443C30 void game_ui_enable() { if (game_ui_disabled) { intface_enable(); kb_enable(); kb_clear(); gmouse_enable(); gmouse_3d_on(); game_ui_disabled = false; } } // game_ui_is_disabled // 0x443C60 bool game_ui_is_disabled() { return game_ui_disabled; } // 0x443C68 int game_get_global_var(int var) { if (var < 0 || var >= num_game_global_vars) { debug_printf("ERROR: attempt to reference global var out of range: %d", var); return 0; } return game_global_vars[var]; } // 0x443C98 int game_set_global_var(int var, int value) { if (var < 0 || var >= num_game_global_vars) { debug_printf("ERROR: attempt to reference global var out of range: %d", var); return -1; } game_global_vars[var] = value; return 0; } // game_load_info // 0x443CC8 int game_load_info() { return game_load_info_vars("data\\vault13.gam", "GAME_GLOBAL_VARS:", &num_game_global_vars, &game_global_vars); } // 0x443CE8 int game_load_info_vars(const char* path, const char* section, int* variablesListLengthPtr, int** variablesListPtr) { inven_reset_dude(); File* stream = db_fopen(path, "rt"); if (stream == NULL) { return -1; } if (*variablesListLengthPtr != 0) { mem_free(*variablesListPtr); *variablesListPtr = NULL; *variablesListLengthPtr = 0; } char string[260]; if (section != NULL) { while (db_fgets(string, 258, stream)) { if (strncmp(string, section, 16) == 0) { break; } } } while (db_fgets(string, 258, stream)) { if (string[0] == '\n') { continue; } if (string[0] == '/' && string[1] == '/') { continue; } char* semicolon = strchr(string, ';'); if (semicolon != NULL) { *semicolon = '\0'; } *variablesListLengthPtr = *variablesListLengthPtr + 1; *variablesListPtr = (int*)mem_realloc(*variablesListPtr, sizeof(int) * *variablesListLengthPtr); if (*variablesListPtr == NULL) { exit(1); } char* equals = strchr(string, '='); if (equals != NULL) { sscanf(equals + 1, "%d", *variablesListPtr + *variablesListLengthPtr - 1); } else { *variablesListPtr[*variablesListLengthPtr - 1] = 0; } } db_fclose(stream); return 0; } // 0x443E2C int game_state() { return game_state_cur; } // 0x443E34 int game_state_request(int a1) { if (a1 == GAME_STATE_0) { a1 = GAME_STATE_1; } else if (a1 == GAME_STATE_2) { a1 = GAME_STATE_3; } else if (a1 == GAME_STATE_4) { a1 = GAME_STATE_5; } if (game_state_cur != GAME_STATE_4 || a1 != GAME_STATE_5) { game_state_cur = a1; return 0; } return -1; } // 0x443E90 void game_state_update() { int v0; v0 = game_state_cur; switch (game_state_cur) { case GAME_STATE_1: v0 = GAME_STATE_0; break; case GAME_STATE_3: v0 = GAME_STATE_2; break; case GAME_STATE_5: v0 = GAME_STATE_4; } game_state_cur = v0; } // NOTE: Unused. // // 0x443EC0 static void game_display_counter(double value) { char stringBuffer[16]; sprintf(stringBuffer, "%f", value); display_print(stringBuffer); } // 0x443EF0 static int game_screendump(int width, int height, unsigned char* buffer, unsigned char* palette) { MessageListItem messageListItem; if (default_screendump(width, height, buffer, palette) != 0) { // Error saving screenshot. messageListItem.num = 8; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } // Saved screenshot. messageListItem.num = 3; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } return 0; } // NOTE: Inlined. // // 0x443F50 static void game_unload_info() { num_game_global_vars = 0; if (game_global_vars != NULL) { mem_free(game_global_vars); game_global_vars = NULL; } } // 0x443F74 static void game_help() { bool isoWasEnabled = map_disable_bk_processes(); gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_NONE); bool colorCycleWasEnabled = cycle_is_enabled(); cycle_disable(); int helpWindowX = 0; int helpWindowY = 0; int win = win_add(helpWindowX, helpWindowY, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, 0, WINDOW_HIDDEN | WINDOW_FLAG_0x04); if (win != -1) { unsigned char* windowBuffer = win_get_buf(win); if (windowBuffer != NULL) { int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 297, 0, 0, 0); CacheEntry* backgroundHandle; unsigned char* backgroundData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundHandle); if (backgroundData != NULL) { palette_set_to(black_palette); buf_to_buf(backgroundData, HELP_SCREEN_WIDTH, HELP_SCREEN_HEIGHT, HELP_SCREEN_WIDTH, windowBuffer, HELP_SCREEN_WIDTH); art_ptr_unlock(backgroundHandle); win_show(win); loadColorTable("art\\intrface\\helpscrn.pal"); palette_set_to(cmap); while (get_input() == -1 && game_user_wants_to_quit == 0) { } while (mouse_get_buttons() != 0) { get_input(); } palette_set_to(black_palette); } } win_delete(win); loadColorTable("color.pal"); palette_set_to(cmap); } if (colorCycleWasEnabled) { cycle_enable(); } gmouse_3d_on(); if (isoWasEnabled) { map_enable_bk_processes(); } } // 0x4440B8 int game_quit_with_confirm() { bool isoWasEnabled = map_disable_bk_processes(); bool gameMouseWasVisible; if (isoWasEnabled) { gameMouseWasVisible = gmouse_3d_is_on(); } else { gameMouseWasVisible = false; } if (gameMouseWasVisible) { gmouse_3d_off(); } bool cursorWasHidden = mouse_hidden(); if (cursorWasHidden) { mouse_show(); } int oldCursor = gmouse_get_cursor(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); int rc; // Are you sure you want to quit? MessageListItem messageListItem; messageListItem.num = 0; if (message_search(&misc_message_file, &messageListItem)) { rc = dialog_out(messageListItem.text, 0, 0, 169, 117, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO); if (rc != 0) { game_user_wants_to_quit = 2; } } else { rc = -1; } gmouse_set_cursor(oldCursor); if (cursorWasHidden) { mouse_hide(); } if (gameMouseWasVisible) { gmouse_3d_on(); } if (isoWasEnabled) { map_enable_bk_processes(); } return rc; } // 0x44418C static int game_init_databases() { int hashing; char* main_file_name; char* patch_file_name; int patch_index; char filename[MAX_PATH]; hashing = 0; main_file_name = NULL; patch_file_name = NULL; if (config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, &hashing)) { db_enable_hash_table(); } config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, &main_file_name); if (*main_file_name == '\0') { main_file_name = NULL; } config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patch_file_name); if (*patch_file_name == '\0') { patch_file_name = NULL; } master_db_handle = db_init(main_file_name, 0, patch_file_name, 1); if (master_db_handle == -1) { GNWSystemError("Could not find the master datafile. Please make sure the FALLOUT CD is in the drive and that you are running FALLOUT from the directory you installed it to."); return -1; } config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, &main_file_name); if (*main_file_name == '\0') { main_file_name = NULL; } config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, &patch_file_name); if (*patch_file_name == '\0') { patch_file_name = NULL; } critter_db_handle = db_init(main_file_name, 0, patch_file_name, 1); if (critter_db_handle == -1) { db_select(master_db_handle); GNWSystemError("Could not find the critter datafile. Please make sure the FALLOUT CD is in the drive and that you are running FALLOUT from the directory you installed it to."); return -1; } for (patch_index = 0; patch_index < 1000; patch_index++) { sprintf(filename, "patch%03d.dat", patch_index); if (access(filename, 0) == 0) { db_init(filename, 0, NULL, 1); } } db_select(master_db_handle); return 0; } // 0x444384 static void game_splash_screen() { int splash; config_get_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, &splash); char path[64]; char* language; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language) && stricmp(language, ENGLISH) != 0) { sprintf(path, "art\\%s\\splash\\", language); } else { sprintf(path, "art\\splash\\"); } File* stream; for (int index = 0; index < SPLASH_COUNT; index++) { char filePath[64]; sprintf(filePath, "%ssplash%d.rix", path, splash); stream = db_fopen(filePath, "rb"); if (stream != NULL) { break; } splash++; if (splash >= SPLASH_COUNT) { splash = 0; } } if (stream == NULL) { return; } unsigned char* palette = (unsigned char*)mem_malloc(768); if (palette == NULL) { db_fclose(stream); return; } unsigned char* data = (unsigned char*)mem_malloc(SPLASH_WIDTH * SPLASH_HEIGHT); if (data == NULL) { mem_free(palette); db_fclose(stream); return; } palette_set_to(black_palette); db_fseek(stream, 10, SEEK_SET); db_fread(palette, 1, 768, stream); db_fread(data, 1, SPLASH_WIDTH * SPLASH_HEIGHT, stream); db_fclose(stream); int splashWindowX = 0; int splashWindowY = 0; scr_blit(data, SPLASH_WIDTH, SPLASH_HEIGHT, 0, 0, SPLASH_WIDTH, SPLASH_HEIGHT, splashWindowX, splashWindowY); palette_fade_to(palette); mem_free(data); mem_free(palette); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, splash + 1); } ================================================ FILE: src/game/game.h ================================================ #ifndef FALLOUT_GAME_GAME_H_ #define FALLOUT_GAME_GAME_H_ #include #include "game/game_vars.h" #include "game/message.h" typedef enum GameState { GAME_STATE_0, GAME_STATE_1, GAME_STATE_2, GAME_STATE_3, GAME_STATE_4, GAME_STATE_5, } GameState; extern int* game_global_vars; extern int num_game_global_vars; extern const char* msg_path; extern int game_user_wants_to_quit; extern MessageList misc_message_file; extern int master_db_handle; extern int critter_db_handle; int game_init(const char* windowTitle, bool isMapper, int a3, int a4, int argc, char** argv); void game_reset(); void game_exit(); int game_handle_input(int eventCode, bool isInCombatMode); void game_ui_disable(int a1); void game_ui_enable(); bool game_ui_is_disabled(); int game_get_global_var(int var); int game_set_global_var(int var, int value); int game_load_info(); int game_load_info_vars(const char* path, const char* section, int* variablesListLengthPtr, int** variablesListPtr); int game_state(); int game_state_request(int a1); void game_state_update(); int game_quit_with_confirm(); #endif /* FALLOUT_GAME_GAME_H_ */ ================================================ FILE: src/game/game_vars.h ================================================ #ifndef GAME_VARS_H #define GAME_VARS_H typedef enum GameGlobalVar { GVAR_PLAYER_REPUTATION, GVAR_CHILDKILLER_REPUTATION, GVAR_CHAMPION_REPUTATION, GVAR_BERSERKER_REPUTATION, GVAR_BAD_MONSTER, GVAR_GOOD_MONSTER, GVAR_PLAYER_MARRIED, GVAR_ENEMY_ARROYO, GVAR_KNOWLEDGE_HEALING_POWDER, GVAR_KILL_EVIL_PLANTS, GVAR_START_ARROYO_TRIAL, GVAR_REPUTATION_SLAVER, GVAR_REPUTATION_SLAVE_OWNER, GVAR_DEN_MOM_STATUS, GVAR_ENEMY_DEN, GVAR_EXILE_DEN, GVAR_DEN_ANNA_STATUS, GVAR_DEN_WAREHOUSE_ACCESS, GVAR_PLAYER_GOT_CAR, GVAR_DEN_VIC_STATUS, GVAR_DEN_MAGGIE_STILL, GVAR_NUKA_COLA_ADDICT, GVAR_BUFF_OUT_ADDICT, GVAR_MENTATS_ADDICT, GVAR_PSYCHO_ADDICT, GVAR_RADAWAY_ADDICT, GVAR_ALCOHOL_ADDICT, GVAR_LOAD_MAP_INDEX, GVAR_RUNNING_BURNING_GUY, GVAR_VIC_DEVICE, GVAR_SLAVE_RUN, GVAR_SLAVES_COUNT, GVAR_MAGGIE_STATUS, GVAR_SLAVES_LOST, GVAR_SLAVERS_LOST, GVAR_PIP_BOY_ANNA_DIARY, GVAR_FRANKIE_STATUS, GVAR_KARMA_HOLY_WARRIOR, GVAR_KARMA_GUARDIAN_OF_THE_WASTES, GVAR_KARMA_SHIELD_OF_HOPE, GVAR_KARMA_DEFENDER, GVAR_KARMA_WANDERER, GVAR_KARMA_BETRAYER, GVAR_KARMA_SWORD_OF_DESPAIR, GVAR_KARMA_SCOURGE_OF_THE_WASTE, GVAR_KARMA_DEMON_SPAWN, GVAR_MAP_EXIT_TILE, GVAR_TOWN_REP_ARROYO, GVAR_TOWN_REP_KLAMATH, GVAR_TOWN_REP_THE_DEN, GVAR_TOWN_REP_VAULT_CITY, GVAR_TOWN_REP_GECKO, GVAR_TOWN_REP_MODOC, GVAR_TOWN_REP_SIERRA_BASE, GVAR_TOWN_REP_BROKEN_HILLS, GVAR_TOWN_REP_NEW_RENO, GVAR_TOWN_REP_REDDING, GVAR_TOWN_REP_NCR, GVAR_TOWN_REP_BURIED_VAULT, GVAR_TOWN_REP_VAULT_13, GVAR_TOWN_REP_COLUSA, GVAR_TOWN_REP_SAN_FRANCISCO, GVAR_TOWN_REP_ENCLAVE, GVAR_TOWN_REP_ABBEY, GVAR_TOWN_REP_EPA, GVAR_TOWN_REP_PRIMITIVE_TRIBE, GVAR_TOWN_REP_RAIDERS, GVAR_MAP_NEXT_TILE, GVAR_ENEMY_KLAMATH, GVAR_TORR_HARMED, GVAR_TORR_DEAD, GVAR_TORR_MISSING, GVAR_TORR_SEARCH_SUCCESS, GVAR_TRAPPER_RETURNED, GVAR_DUNTONS_ANGRY, GVAR_RUSTLE_FAIL_VIOLENT, GVAR_RUSTLE_FAIL, GVAR_RUSTLE_SUCCESS, GVAR_TORR_GUARD_SUCCESS, GVAR_VAULT_CITIZEN, GVAR_VAULT_PLOW_PROBLEM, GVAR_VAULT_CITIZENSHIP, GVAR_VAULT_GECKO_PLANT, GVAR_VAULT_PLANT_STATUS, GVAR_VAULT_REDDING_PROBLEM, GVAR_JET_QUEST, GVAR_DAY_PASS_SHOWN, GVAR_VAULT_CITIZEN_TEST, GVAR_VAULT_RAIDERS, GVAR_VAULT_DELIVER_HOLODISK, GVAR_VAULT_FIND_THOMAS, GVAR_QUEST_VAULT_CITIZEN, GVAR_QUEST_PLOW_PROBLEM, GVAR_QUEST_GECKO_PLANT, GVAR_QUEST_REDDING_PROBLEM, GVAR_QUEST_JET_QUEST, GVAR_QUEST_RAIDERS, GVAR_QUEST_DELIVER_HOLODISK, GVAR_QUEST_FIND_THOMAS, GVAR_MODOC_KILL_ALL_BRAHMIN_TIME, GVAR_QUEST_VIC_DEVICE, GVAR_QUEST_MAGGIE_STILL, GVAR_QUEST_KILL_EVIL_PLANTS, GVAR_QUEST_RUSTLE_CATTLE, GVAR_BUST_SKEEVE, GVAR_DUDE_STOMACH, GVAR_MODOC_FAMILY_FEUD_SEED_ONE, GVAR_MODOC_FAMILY_FEUD_SEED_TWO, GVAR_MODOC_BRAHMIN_SEED, GVAR_MODOC_KARL_PIP, GVAR_MODOC_KARL_SEED, GVAR_MODOC_VERMIN_HUNTER_SEED, GVAR_MODOC_GHOST_FARM_SEED, GVAR_SLAG_ATTACK, GVAR_JONNY_STATE, GVAR_JONNY_TILE, GVAR_MODOC_BRAHMIN_ALIVE, GVAR_MODOC_DOGS_ALIVE, GVAR_MODOC_TOOL_FLAG, GVAR_MODOC_SLAUGHTER_BESS_TIME, GVAR_KARL_STATE, GVAR_MODOC_BODIES, GVAR_MODOC_SLAUGHTER_FLAG, GVAR_MODOC_ROSE_FLAG, GVAR_MODOC_TANNERY_FLAG, GVAR_MODOC_POST_FLAG, GVAR_HOSTILE_SLAVE_COUNT, GVAR_LADDIE_STATE, GVAR_LADDIE_TILE, GVAR_MODOC_JONNY_HOME, GVAR_MODOC_SPOKE_PROTECTOR, GVAR_MODOC_MESSAGE, GVAR_MUTATE, GVAR_MUTATE_WHEN, GVAR_SALVATORE_FAMILY_COUNTER, GVAR_BISHOP_FAMILY_COUNTER, GVAR_MORDINO_FAMILY_COUNTER, GVAR_ENEMY_VAULT_CITY, GVAR_VAULT_GET_LYNETTE_REWARD, GVAR_VAULT_GET_MCCLURE_PART, GVAR_VAULT_SERVANT, GVAR_VAULT_VILLAGE, GVAR_QUEST_VAULT_SERVANT, GVAR_QUEST_VAULT_VILLAGE, GVAR_VAULT_MONSTER_COUNT, GVAR_SERVANT_RAID_DATE, GVAR_ENEMY_VAULT_VILLAGE, GVAR_BROKEN_HILLS_FRAUD, GVAR_VAULT_BEEN_TO_RAIDERS, GVAR_SIERRA_BASE_CONTAMINATION_TIMER, GVAR_SIERRA_BASE_LEVEL_BREACH, GVAR_SIERRA_BASE_ALERT, GVAR_SIERRA_BASE_ENEMY, GVAR_SIERRA_BASE_POWER, GVAR_SIERRA_BASE_SECURITY, GVAR_BRAIN_BOT_BRAIN, GVAR_SIERRA_LOCKOUT, GVAR_SIERRA_PASSWORD, GVAR_GECKO_ECON_DISK, GVAR_GECKO_REQ_FORM, GVAR_GECKO_SKEETER_PART, GVAR_GECKO_ANKH, GVAR_DEN_SMITTY_PART, GVAR_MCCLURE_KNOWN, GVAR_HOLODISK_SIERRA_EVACUATION, GVAR_HOLODISK_SIERRA_MED_LOG, GVAR_HOLODISK_SIERRA_EXP_LOG, GVAR_GECKO_SKEETER_STATUS, GVAR_NCR_TANDI_WORK, GVAR_NCR_TANDI_JOB_ACCEPT, GVAR_NCR_BEAT_HOSS, GVAR_NCR_SQUAT_DEAL, GVAR_NCR_V15_DARION_DEAD, GVAR_NCR_V15_DARION_DEAL, GVAR_NEWRENO_SNUFF_WESTIN, GVAR_NEWRENO_SNUFF_CARLSON, GVAR_VAULT13_CLEAR, GVAR_NCR_SPY_KNOWN, GVAR_NCR_TANDI_WARN_CARLSON, GVAR_RUSTLE_ACCEPT, GVAR_RUSTLE_REFUSE, GVAR_RUSTLE_REWARD, GVAR_TORR_GUARD_STATUS, GVAR_ARROYO_SPEAR, GVAR_RUSTLE_OVER, GVAR_NCR_BRAHMIN_PROTECT, GVAR_NCR_DEATHCLAW_DEN, GVAR_SLAVE_RUN_KILLED, GVAR_DUNTON_DEAD, GVAR_NCR_CAR_JACKED, GVAR_NCR_MERK_WORK, GVAR_ARROYO_DOG, GVAR_HAVE_MUTATED, GVAR_MUTATE_STAGE, GVAR_PLAYER_SEX_LEVEL, GVAR_NCR_VORTIS_QUEST_STATE, GVAR_NCR_RANGERS_KNOWN, GVAR_SMILEY_STATUS, GVAR_STILL_STATUS, GVAR_STILL_FAILURE, GVAR_GRAVE_FLAGS_1, GVAR_GRAVE_FLAGS_2, GVAR_TORR_BRAHMIN_KILLED, GVAR_ENEMY_TORR, GVAR_ENEMY_DUNTON, GVAR_ENEMY_SMILEY, GVAR_NCR_SCMERK_HEREBEFORE, GVAR_NCR_SCMERK_HOSTILE, GVAR_NCR_SCMERK_PERSONAL_ENEMY, GVAR_NCR_SCMERK_STATUS, GVAR_NCR_SCMERK_SEED_STATUS, GVAR_NCR_LENNY_MET, GVAR_NCR_ELRON_ADJUST, GVAR_NCR_FAKE_VAULT13_MAP, GVAR_NCR_FAKE_VAULT13_HOLODISK, GVAR_MILITARY_BASE_FLAGS, GVAR_WRIGHT_FAMILY_COUNTER, GVAR_NCR_MIRA_STATE, GVAR_NCR_ROPE_KNOWN, GVAR_NEW_RENO_WARNING_TIMER, GVAR_HOLODISK_MB_OUTSIDE, GVAR_HOLODISK_MB_LEVEL_1, GVAR_HOLODISK_MB_LEVEL_2, GVAR_HOLODISK_MB_LEVEL_3, GVAR_HOLODISK_MB_LEVEL_4, GVAR_NCR_GTEGRD_ATTACK, GVAR_NCR_GATE_NIGHT, GVAR_NCR_ENCLAVE_INFO, GVAR_NCR_WESTIN_SEED, GVAR_NCR_DOROTHY_SEED, GVAR_NEW_RENO_MADE_MAN, GVAR_NEW_RENO_PRIZEFIGHTER, GVAR_NEW_RENO_PORN_STAR, GVAR_VAULT13_FOUND_GECK, GVAR_NCR_POWER_ON, GVAR_SULIK_FREE, GVAR_TORR_SEARCH_ACCEPT, GVAR_NCR_HENRY_HYPO, GVAR_ENEMY_GECKO, GVAR_GECKO_COOLANT, GVAR_NCR_POWERPLANT, GVAR_NCR_PLAYER_RANGER, GVAR_NCR_JACK_STATE, GVAR_8_BALL_TOILET_SECRET, GVAR_8_BALL_TRASH_SECRET, GVAR_NCR_GENERIC_STATE, GVAR_NEW_RENO_MCGEE_SEED, GVAR_NEW_RENO_MCGEE_KNOWN, GVAR_NEW_RENO_MCGEE_ATTACKED, GVAR_GECKO_BRAIN_DEAD, GVAR_GECKO_BRAIN_FRIEND, GVAR_SALVATORE_WARNINGS, GVAR_BISHOP_WARNINGS, GVAR_MORDINO_WARNINGS, GVAR_WRIGHT_WARNINGS, GVAR_NEW_RENO_BISHOP, GVAR_NCR_SNUFF_BISHOP, GVAR_NEW_RENO_CARLSON_PRICE, GVAR_NEW_RENO_WESTIN_PRICE, GVAR_NEW_RENO_HAS_REP_PRIZEFIGHTER, GVAR_NEW_RENO_ANGELA, GVAR_PLACEHOLDER_002, GVAR_NCR_ELISE_SEED, GVAR_NEW_RENO_MRS_BISHOP, GVAR_NCR_FELIX_SEED, GVAR_NCR_BISHOP_PRICE, GVAR_NCR_CATTLE_DRIVE, GVAR_NCR_CATTLE_TIME_MIN, GVAR_NCR_CATTLE_TIME_MAX, GVAR_CARAVAN_STATUS, GVAR_CARAVAN_START, GVAR_CARAVAN_END, GVAR_CARAVAN_DRIVERS, GVAR_CARAVAN_GUARDS, GVAR_CARAVAN_CARTS, GVAR_CARAVAN_ENCOUNTERS, GVAR_CARAVAN_BRAHMIN, GVAR_CARAVAN_MASTERS, GVAR_CARAVAN_DRIVERS_TOTAL, GVAR_CARAVAN_GUARDS_TOTAL, GVAR_CARAVAN_CARTS_TOTAL, GVAR_CARAVAN_BRAHMIN_TOTAL, GVAR_CARAVAN_MASTERS_TOTAL, GVAR_CARAVAN_ENCOUNTERS_TOTAL, GVAR_NEW_RENO_MYRON, GVAR_NEW_RENO_WRIGHT_FLAGS, GVAR_NEW_RENO_WRIGHT_MYSTERY, GVAR_DEN_SLAVER_WARNINGS, GVAR_V13_V15_DALIA_STATE, GVAR_PARTY_CHILDKILLER, GVAR_MODOC_STAGE_TIMER, GVAR_MODOC_STAGE_STATE, GVAR_REDDING_WHORE_CUT, GVAR_V15_SEED_STATUS, GVAR_TOWN_REP_VAULT_15, GVAR_ADDICT_TRAGIC, GVAR_ADDICT_JET, GVAR_MODOC_GENERIC_FLAG_1, GVAR_DEN_CEASAR_STATUS, GVAR_MODOC_BRAHMIN_ESCAPED, GVAR_BH_CHAD, GVAR_BH_FTM, GVAR_BH_MINE, GVAR_BH_JAIL, GVAR_BH_CONSPIRACY, GVAR_BH_MISSING, GVAR_BH_MIGHTY_MAN, GVAR_BH_MINING, GVAR_TOWN_REP_GHOST_FARM, GVAR_ENEMY_BROKEN_HILLS, GVAR_SLAG_CNT, GVAR_NEW_RENO_SALVATORE_RESPECT, GVAR_NEW_RENO_TRACK_LLOYD, GVAR_NEW_RENO_GUARD_ASSIGNMENT, GVAR_NEW_RENO_FLAG_1, GVAR_NEW_RENO_SALVATORE, GVAR_NEW_RENO_TRIBUTE, GVAR_NEW_RENO_SALVATORE_PISTOL, GVAR_NEW_RENO_ESCAPE, GVAR_GRAVES_UNEARTHED, GVAR_MOORE_STATE, GVAR_MOORE_ACCEPT_DELIVERY, GVAR_MOORE_REFUSE_DELIVERY, GVAR_SPECIAL_ENCOUNTER_FLAGS, GVAR_BH_BOSS, GVAR_BH_HENCH_COUNT, GVAR_BH_MALE_NAMES_USED, GVAR_BH_FEMALE_NAMES_USED, GVAR_BH_HENCH_KILLED, GVAR_BH_CHECKED, GVAR_BH_CARAVAN, GVAR_BH_RANK_KILLED, GVAR_REDDING_EXCAVATOR_CHIP, GVAR_REDDING_JET_LEVEL, GVAR_MAYOR_REDDING_STATUS, GVAR_REDDING_MARGE_STATUS, GVAR_REDDING_DAN_STATUS, GVAR_REDDING_JOHNSON_STATUS, GVAR_CATTLE_DRIVE_CARAVAN, GVAR_MEDICINE_CARAVAN, GVAR_JET_CARAVAN, GVAR_GOLD_CARAVAN, GVAR_REDDING_CARAVAN_STATUS, GVAR_NEW_RENO_SAD, GVAR_NEW_RENO_WRIGHT_STILL, GVAR_NEW_RENO_FLAG_2, GVAR_NEW_RENO_WRIGHT_STILL_MISSION, GVAR_NEW_RENO_JULES_KITTY, GVAR_NEW_RENO_STOLEN_CAR, GVAR_NEW_RENO_JULES_ELDRIDGE, GVAR_GRUTHAR_DSTATUS, GVAR_WHIRLY, GVAR_MYSTERIOUS_STRANGER, GVAR_MYSTERIOUS_STRANGER_LEVEL, GVAR_NEW_RENO_DELIVERY, GVAR_NEW_RENO_EXTORTION_BROS, GVAR_NEW_RENO_ASSASSINATION, GVAR_NEW_RENO_LIL_JESUS_REFERS, GVAR_SEX_COUNTER, GVAR_RND_SALES_NAME, GVAR_RND_SALES_ENCOUNTER, GVAR_SAN_FRAN_FLAGS, GVAR_SAN_FRAN_SUB, GVAR_SAN_FRAN_TANKER, GVAR_SAN_FRAN_SHIHACKED, GVAR_SAN_FRAN_BADGER, GVAR_SAN_FRAN_ELRON, GVAR_SAN_FRAN_SPLEEN, GVAR_KNOW_DOC_HOLIDAY, GVAR_DUMAR_STATUS, GVAR_NEW_RENO_JET_SOURCE, GVAR_DEN_BECKY_JOB, GVAR_HOLY_GRENADE, GVAR_RAIDERS_FLAGS, GVAR_DEN_FRED_STATUS, GVAR_DEN_DEREK_STATUS, GVAR_DEN_ROBBY_STATUS, GVAR_RAIDERS_COUNT, GVAR_DEN_HEATHER_STATUS, GVAR_SUPER_CAR, GVAR_BAR_BRAWL, GVAR_WADE_STATUS, GVAR_STANWELL_STATUS, GVAR_SAVINELLI_STATUS, GVAR_IMPLANTS_KNOWN, GVAR_FROG_MORTON, GVAR_REDDING_MORTON_BROTHERS, GVAR_REDDING_SHERIFF, GVAR_MODOC_ENDINGS, GVAR_WANAMINGO_OCCUPADO, GVAR_QUEST_RAT_GOD, GVAR_QUEST_RESCUE_TORR, GVAR_VAULT_JET_SOURCE, GVAR_QUEST_SUPER_REPAIR_KIT, GVAR_QUEST_PLASMA_TRANSFORMER, GVAR_GECKO_MELTDOWN, GVAR_QUEST_REPAIR_POWER_PLANT, GVAR_QUEST_OPTIMIZE_POWER_PLANT, GVAR_PARTY_NO_FOLLOW, GVAR_RND_KAGA_STATE, GVAR_VAULT_CITY_VENT, GVAR_VAULT_PIP, GVAR_MODOC_GENERIC_FLAG_2, GVAR_SLAUGHTER_SLAG_TIME, GVAR_PIPBOY_TOUR_GUIDE, GVAR_PIPBOY_CREDITS, GVAR_NCR_GRANT_HOSTILE, GVAR_NCR_WFIELD_NOTIFY, GVAR_ENDGAME_MOVIE_ARROYO, GVAR_ENDGAME_MOVIE_MODOC, GVAR_ENDGAME_MOVIE_DEN, GVAR_ENDGAME_MOVIE_VAULT_CITY, GVAR_ENDGAME_MOVIE_RENO, GVAR_ENDGAME_MOVIE_RENO_ADD1, GVAR_ENDGAME_MOVIE_RENO_ADD2, GVAR_ENDGAME_MOVIE_RENO_ADD3, GVAR_ENDGAME_MOVIE_RENO_ADD4, GVAR_ENDGAME_MOVIE_GECKO, GVAR_ENDGAME_MOVIE_REDDING, GVAR_ENDGAME_MOVIE_BROKEN_HILLS, GVAR_ENDGAME_MOVIE_NCR, GVAR_ENDGAME_MOVIE_VAULT_15, GVAR_ENDGAME_MOVIE_VAULT_13, GVAR_ENDGAME_MOVIE_SAN_FRAN_SHI, GVAR_ENDGAME_MOVIE_SAN_FRAN_ELRON, GVAR_ENDGAME_MOVIE_SAN_FRAN_PUNKS, GVAR_SAN_FRAN_STRUGGLE, GVAR_SAN_FRAN_ELRON_WHIRLY, GVAR_DR_TROY_STATUS, GVAR_V13_STATUS_FLAGS, GVAR_GECKO_TIMER_MELTDOWN, GVAR_ENCLAVE_POWER_PLANT, GVAR_ENCLAVE_GRANITE_JOINED, GVAR_ENCLAVE_ALARM, GVAR_ENCLAVE_TIMER, GVAR_ENCLAVE_REACTOR, GVAR_VAULT_LYNETTE_STATUS, GVAR_DOC_JOHNSON_STATUS, GVAR_NCR_GEN_FLAGS, GVAR_CAR_BLOWER, GVAR_ENCLAVE_COMPUTER, GVAR_ENCLAVE_MARTIN, GVAR_ENCLAVE_ELDER, GVAR_JAIL_BREAK, GVAR_SAN_FRAN_ARMOR, GVAR_DEN_FLAG_1, GVAR_DEN_FLAG_2, GVAR_DEN_FLAG_3, GVAR_SAN_FRAN_SPLEEN_TIME, GVAR_PLAYER_WAS_MARRIED, GVAR_DEN_SMITTY_DELIVER, GVAR_SMITTY_DELIVER_TIME, GVAR_DEN_VIC_KNOWN, GVAR_CAR_UPGRADE_FUEL_CELL_REGULATOR, GVAR_DEN_GANGWAR, GVAR_NEW_RENO_CAR_UPGRADE, GVAR_NEW_RENO_SUPER_CAR, GVAR_DEN_SEE_VIC, GVAR_STILL_START, GVAR_QUEST_JOSHUA, GVAR_ARDIN_FREEDOM, GVAR_TOTAL_WANAMINGOS, GVAR_SAN_FRAN_DAVE, GVAR_VC_MET_ED, GVAR_FRED_MONEY, GVAR_CAN_ASK_ARDIN_ABOUT_SMILEY, GVAR_VAULT_USED_TEACHING_SYSTEM, GVAR_DEN_GANG_1_COUNT, GVAR_DEN_GANG_2_COUNT, GVAR_DEN_GANG_D_DAY, GVAR_DEN_METZGER_GANG_KILL_TIMER, GVAR_DEN_GANG_TRAP, GVAR_DEN_GANG_DOOR, GVAR_V15_CRISSY_QUEST, GVAR_V15_KILL_DARION, GVAR_V15_NCR_DEAL, GVAR_V15_NCR_SPY, GVAR_SAN_FRAN_EG_NOTIFY, GVAR_SAN_FRAN_EG_A_OBJ, GVAR_ELRON_GUARDS, GVAR_ARROYO_RETURN_GECK, GVAR_NCR_BRAHMN_QST, GVAR_NCR_DRPAPR_QST, GVAR_NCR_ELMBISHOP_QST, GVAR_NCR_LYNETTE_HOLO_QST, GVAR_NCR_ENLONE_LETTER_QST, GVAR_NCR_KILL_ELRON_QST, GVAR_V13_COMP_QST, GVAR_V13_GORIS_QST, GVAR_GIMP_FLAG, GVAR_GECKO_ASSIGNED, GVAR_MODOC_SHITTY_DEATH, GVAR_ENEMY_REDDING, GVAR_VAL_TOOLS, GVAR_FALLOUT_2, GVAR_NEW_RENO_FLAG_3, GVAR_MR_BISHOP_SAFE, GVAR_VAULT_BOOZE_SMUGGLING, GVAR_ENCLAVE_COUNTDOWN, GVAR_ENCLAVE_FRANK_DEAD, GVAR_NCR_BRAHMIN_QST, GVAR_NEW_RENO_KITTY_MAGAZINES, GVAR_NCR_FREE_SLAVES_QST, GVAR_NEW_RENO_STUART_DEAL, GVAR_NEW_RENO_FIGHT_LEVEL, GVAR_ENEMY_VAULT_COURTYARD, GVAR_NEW_RENO_ROUND_NUMBER, GVAR_NEW_RENO_ROUND_TIME, GVAR_NEW_RENO_DUDE_SCORE, GVAR_NEW_RENO_BOXER_SCORE, GVAR_NEW_RENO_FIGHT_STATUS, GVAR_NAVARRO_BASE_ALERT, GVAR_NAVARRO_FOB, GVAR_NAVARRO_K9, GVAR_NAVARRO_POWER_CENTER, GVAR_NAVARRO_VERTIBIRDS, GVAR_STANWELL_PAYOUT, GVAR_WADE_PAYOUT, GVAR_SAVINE_PAYOUT, GVAR_SAN_FRAN_SHI_WHIRLY, GVAR_SIERRA_GNN_HOLODISK, GVAR_SIERRA_MISSION_HOLODISK, GVAR_ELRON_HOLODISK, GVAR_NEW_RENO_KILL_DADDY_WEAPON, GVAR_READ_FRANCIS_NOTE, GVAR_ENEMY_CONSPIRATORS, GVAR_MARCUS_DEAD, GVAR_RAIDER_SECRET_ENTRANCE_KNOWN, GVAR_COMING_FROM_INSIDE_RAIDERS, GVAR_VAULT_STARK_RECON, GVAR_NEW_RENO_MRS_BISHOP_COMBINATION, GVAR_TALKED_TO_ELDER, GVAR_SAN_FRAN_FUEL_TANK_QST, GVAR_SAN_FRAN_NAV_TANK_QST, GVAR_SAN_FRAN_FOB_TANK_QST, GVAR_SAN_FRAN_ELRON_GAS_QST, GVAR_SAN_FRAN_BADGER_GFRIEND_QST, GVAR_SAN_FRAN_LOPAN_KDRAGON_QST, GVAR_SAN_FRAN_DRAGON_KLOPAN_QST, GVAR_SAN_FRAN_ARMOR_QST, GVAR_FINISHED_STARK_RECON, GVAR_VAULT_CITY_DESIGNER_NOTES, GVAR_BH_POWER, GVAR_NEW_RENO_SUSPECT_JJJ, GVAR_NEW_RENO_SUSPECT_JULES, GVAR_NEW_RENO_SUSPECT_LIL_JESUS, GVAR_NEW_RENO_SUSPECT_RENESCO, GVAR_NEW_RENO_WESTIN_SNUFF_PIP, GVAR_NEW_RENO_CARLSON_SNUFF_PIP, GVAR_NEW_RENO_ELDRIDGE_PISTOL_QUEST, GVAR_DEN_CAR_PART_PIP, GVAR_DEN_ANNA_LOCKET_PIP, GVAR_NEW_RENO_POISON_STILL_TIME, GVAR_SAN_FRAN_WONG_EAT_TIME, GVAR_NAVARRO_XARN, GVAR_SAN_FRAN_KILL_OZ9_QST, GVAR_NEW_RENO_ETHYL_MEETING_TIME, GVAR_SAN_FRAN_VERTI_STEAL_SHI_QST, GVAR_SAN_FRAN_VERTI_STEAL_ELE_QST, GVAR_SAN_FRAN_KILL_EMP_QST, GVAR_SAN_FRAN_VERTI_SHI_QST, GVAR_SAN_FRAN_VERTI_ELE_QST, GVAR_BROKEN_HILLS_CARAVAN_POOCH_SCREW, GVAR_CHAD_DEAD, GVAR_SAN_FRAN_JASHUA_STATUS, GVAR_SAN_FRAN_BOS_QUEST, GVAR_NCR_GUARDS_CHECK_OBJ, GVAR_ENEMY_BANK_GUARDS, GVAR_ENCLAVE_TURRET_GUARD, GVAR_ENCLAVE_TURRET_DETENTION, GVAR_ENCLAVE_TURRET_SCIENCE, GVAR_ENCLAVE_TURRET_PRESIDENT, GVAR_ENCLAVE_TURRET_MAIN, GVAR_HOLODISK_ENCLAVE_SECURITY, GVAR_HOLODISK_ENCLAVE_STATE, GVAR_HOLODISK_ENCLAVE_WORD, GVAR_HOLODISK_ENCLAVE_CHEMICAL, GVAR_HOLODISK_ENCLAVE_ATOMIC, GVAR_ENCLAVE_TURRET_HELP_PLAYER, GVAR_NEW_RENO_GUARD_MESSAGE_TIMER, GVAR_MORTON_GANG, GVAR_GECKO_WORKING_ON_PLANT, GVAR_VIGNETTE_SEQUENCE, GVAR_PLANT_SCHEDULED_FOR_CHANGE, GVAR_DROP_PLAYER_BY_VAULT_8, GVAR_ENCLAVE_COM_LINE, GVAR_LEFT_CAR_AT_RAIDERS, GVAR_RAIDERS_CAR_ELEVATION, GVAR_SEXPERT, GVAR_GIGALO, GVAR_DUDE_VIRGIN, GVAR_MADE_MAN_SALVATORE, GVAR_MADE_MAN_BISHOP, GVAR_MADE_MAN_MORDINO, GVAR_MADE_MAN_WRIGHT, GVAR_NCR_SPY_HOLO_DOWNLOAD, GVAR_NCR_HISTORY_HOLO_DOWNLOAD, GVAR_NCR_WESTIN_HOLO_DOWNLOAD, GVAR_TYPHON_QUEST_STATUS, GVAR_8_BALL_VAULT_TERMINAL, GVAR_RAIDERS_DEAD, GVAR_KLAMATH_GENERATOR, GVAR_ENTERED_GUARDIAN, GVAR_BATH_HOUSE_REJECT, GVAR_SKYNET, GVAR_SPECIAL_ENCOUNTER_BRIDGE, GVAR_SPECIAL_ENCOUNTER_HOLY2, GVAR_SPECIAL_ENCOUNTER_TOXIC, GVAR_SPECIAL_ENCOUNTER_PARIAH, GVAR_SPECIAL_ENCOUNTER_BRAHMIN, GVAR_SPECIAL_ENCOUNTER_WHALE, GVAR_SPECIAL_ENCOUNTER_HEAD, GVAR_SPECIAL_ENCOUNTER_SHUTTLE, GVAR_SPECIAL_ENCOUNTER_GUARDIAN, GVAR_SPECIAL_ENCOUNTER_HOLY1, GVAR_SPECIAL_ENCOUNTER_WOODSMAN, GVAR_GECKO_FIND_WOODY, GVAR_SPECIAL_ENCOUNTER_CAFE, GVAR_GECKO_DESCENDANT_KNOWN, GVAR_FIND_VIC, GVAR_SPECIAL_ENCOUNTER_UNWASHED, GVAR_KLAMATH_SCORPIONS_KILLED, GVAR_KLAMATH_SCORPIONS_TOTAL, GVAR_ENCLAVE_ENEMY_GUARD, GVAR_ENCLAVE_ENEMY_PRESIDENT, GVAR_ENCLAVE_ENEMY_TRAPS, GVAR_ENCLAVE_ENEMY_REACTOR, GVAR_ENCLAVE_ENEMY_DETENTION, GVAR_TOWN_REP_NAVARRO, GVAR_GAVE_GECK_EXP, GVAR_DUDE_START_SEQ_1, GVAR_MODOC_GHOST_SEED_PIP, GVAR_PARTY_MEMBERS_HIDDEN, GVAR_CAR_PLACED_TILE, GVAR_RESERVED_VAR1, GVAR_RESERVED_VAR2, GVAR_RESERVED_VAR3, GVAR_RESERVED_VAR4, GVAR_RESERVED_VAR5, GVAR_RESERVED_VAR6, GVAR_RESERVED_VAR7, GVAR_RESERVED_VAR8, GVAR_RESERVED_VAR9, GVAR_RESERVED_VAR10, GVAR_RESERVED_VAR11, GVAR_RESERVED_VAR12, GVAR_RESERVED_VAR13, GVAR_RESERVED_VAR14, GVAR_RESERVED_VAR15, GVAR_RESERVED_VAR16, GVAR_RESERVED_VAR17, GVAR_RESERVED_VAR18, GVAR_RESERVED_VAR19, GVAR_RESERVED_VAR20, GVAR_RESERVED_VAR21, GVAR_RESERVED_VAR22, GVAR_RESERVED_VAR23, GVAR_RESERVED_VAR24, GVAR_RESERVED_VAR25, GVAR_RESERVED_VAR26, GVAR_RESERVED_VAR27, GVAR_RESERVED_VAR28, GVAR_RESERVED_VAR29, GVAR_RESERVED_VAR30, GVAR_RESERVED_VAR31, GVAR_RESERVED_VAR32, GVAR_RESERVED_VAR33, GVAR_RESERVED_VAR34, GVAR_RESERVED_VAR35, GVAR_RESERVED_VAR36, GVAR_RESERVED_VAR37, GVAR_RESERVED_VAR38, GVAR_RESERVED_VAR39, GVAR_RESERVED_VAR40, GVAR_RESERVED_VAR41, GVAR_RESERVED_VAR42, GVAR_RESERVED_VAR43, GVAR_RESERVED_VAR44, GVAR_RESERVED_VAR45, GVAR_RESERVED_VAR46, GVAR_RESERVED_VAR47, GVAR_RESERVED_VAR48, GVAR_RESERVED_VAR49, GVAR_RESERVED_VAR50, GVAR_RESERVED_VAR51, GVAR_RESERVED_VAR52, GVAR_RESERVED_VAR53, GVAR_RESERVED_VAR54, GVAR_RESERVED_VAR55, GVAR_RESERVED_VAR56, GVAR_RESERVED_VAR57, GVAR_RESERVED_VAR58, GVAR_RESERVED_VAR59, GVAR_MODOC_JONNY_PIP, GVAR_NEW_RENO_FLAG_4, GVAR_PATCH_INVAIDITATOR, } GameGlobalVar; #endif /* GAME_VARS_H */ ================================================ FILE: src/game/gconfig.c ================================================ #include "game/gconfig.h" #include #include // A flag indicating if [game_config] was initialized. // // 0x5186D0 static bool gconfig_initialized = false; // fallout2.cfg // // 0x58E950 Config game_config; // NOTE: There are additional 4 bytes following this array at 0x58EA7C, which // probably means it's size is 264 bytes. // // 0x58E978 static char gconfig_file_name[FILENAME_MAX]; // Inits main game config. // // [isMapper] is a flag indicating whether we're initing config for a main // game, or a mapper. This value is `false` for the game itself. // // [argc] and [argv] are command line arguments. The engine assumes there is // at least 1 element which is executable path at index 0. There is no // additional check for [argc], so it will crash if you pass NULL, or an empty // array into [argv]. // // The executable path from [argv] is used resolve path to `fallout2.cfg`, // which should be in the same folder. This function provide defaults if // `fallout2.cfg` is not present, or cannot be read for any reason. // // Finally, this function merges key-value pairs from [argv] if any, see // [config_cmd_line_parse] for expected format. // // 0x444570 bool gconfig_init(bool isMapper, int argc, char** argv) { if (gconfig_initialized) { return false; } if (!config_init(&game_config)) { return false; } // Initialize defaults. config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, "game"); config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_DAT_KEY, "master.dat"); config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, "data"); config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_DAT_KEY, "critter.dat"); config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_CRITTER_PATCHES_KEY, "data"); config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, ENGLISH); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SCROLL_LOCK_KEY, 0); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_INTERRUPT_WALK_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_ART_CACHE_SIZE_KEY, 8); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_COLOR_CYCLING_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_HASHING_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_SPLASH_KEY, 0); config_set_value(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_FREE_SPACE_KEY, 20480); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, 1); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 1); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 3); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 2); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 1); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, 1); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, 1); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, 0); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, 0); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, 0); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEED_KEY, 0); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, 3.5); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 1.399994); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, 1.0); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, 1.0); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEVICE_KEY, -1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_PORT_KEY, -1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_IRQ_KEY, -1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DMA_KEY, -1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, 1); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, 22281); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, 22281); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, 22281); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, 22281); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, 448); config_set_string(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH1_KEY, "sound\\music\\"); config_set_string(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_PATH2_KEY, "sound\\music\\"); config_set_string(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_MODE_KEY, "environment"); config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_TILE_NUM_KEY, 0); config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, 0); config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_LOAD_INFO_KEY, 0); config_set_value(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_OUTPUT_MAP_DATA_INFO_KEY, 0); if (isMapper) { config_set_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, "mapper"); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_OVERRIDE_LIBRARIAN_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_LIBRARIAN_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_USE_ART_NOT_PROTOS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_REBUILD_PROTOS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_OBJECTS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_IGNORE_REBUILD_ERRORS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SHOW_PID_NUMBERS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SAVE_TEXT_MAPS_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_RUN_MAPPER_AS_GAME_KEY, 0); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_DEFAULT_F8_AS_GAME_KEY, 1); config_set_value(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_SORT_SCRIPT_LIST_KEY, 0); } // Make `fallout2.cfg` file path. char* executable = argv[0]; char* ch = strrchr(executable, '\\'); if (ch != NULL) { *ch = '\0'; sprintf(gconfig_file_name, "%s\\%s", executable, GAME_CONFIG_FILE_NAME); *ch = '\\'; } else { strcpy(gconfig_file_name, GAME_CONFIG_FILE_NAME); } // Read contents of `fallout2.cfg` into config. The values from the file // will override the defaults above. config_load(&game_config, gconfig_file_name, false); // Add key-values from command line, which overrides both defaults and // whatever was loaded from `fallout2.cfg`. config_cmd_line_parse(&game_config, argc, argv); gconfig_initialized = true; return true; } // Saves game config into `fallout2.cfg`. // // 0x444C14 bool gconfig_save() { if (!gconfig_initialized) { return false; } if (!config_save(&game_config, gconfig_file_name, false)) { return false; } return true; } // Frees game config, optionally saving it. // // 0x444C3C bool gconfig_exit(bool shouldSave) { if (!gconfig_initialized) { return false; } bool result = true; if (shouldSave) { if (!config_save(&game_config, gconfig_file_name, false)) { result = false; } } config_exit(&game_config); gconfig_initialized = false; return result; } ================================================ FILE: src/game/gconfig.h ================================================ #ifndef FALLOUT_GAME_GCONFIG_H_ #define FALLOUT_GAME_GCONFIG_H_ #include #include "game/config.h" // The file name of the main config file. #define GAME_CONFIG_FILE_NAME "fallout2.cfg" #define GAME_CONFIG_SYSTEM_KEY "system" #define GAME_CONFIG_PREFERENCES_KEY "preferences" #define GAME_CONFIG_SOUND_KEY "sound" #define GAME_CONFIG_MAPPER_KEY "mapper" #define GAME_CONFIG_DEBUG_KEY "debug" #define GAME_CONFIG_EXECUTABLE_KEY "executable" #define GAME_CONFIG_MASTER_DAT_KEY "master_dat" #define GAME_CONFIG_MASTER_PATCHES_KEY "master_patches" #define GAME_CONFIG_CRITTER_DAT_KEY "critter_dat" #define GAME_CONFIG_CRITTER_PATCHES_KEY "critter_patches" #define GAME_CONFIG_PATCHES_KEY "patches" #define GAME_CONFIG_LANGUAGE_KEY "language" #define GAME_CONFIG_SCROLL_LOCK_KEY "scroll_lock" #define GAME_CONFIG_INTERRUPT_WALK_KEY "interrupt_walk" #define GAME_CONFIG_ART_CACHE_SIZE_KEY "art_cache_size" #define GAME_CONFIG_COLOR_CYCLING_KEY "color_cycling" #define GAME_CONFIG_CYCLE_SPEED_FACTOR_KEY "cycle_speed_factor" #define GAME_CONFIG_HASHING_KEY "hashing" #define GAME_CONFIG_SPLASH_KEY "splash" #define GAME_CONFIG_FREE_SPACE_KEY "free_space" #define GAME_CONFIG_TIMES_RUN_KEY "times_run" #define GAME_CONFIG_GAME_DIFFICULTY_KEY "game_difficulty" #define GAME_CONFIG_RUNNING_BURNING_GUY_KEY "running_burning_guy" #define GAME_CONFIG_COMBAT_DIFFICULTY_KEY "combat_difficulty" #define GAME_CONFIG_VIOLENCE_LEVEL_KEY "violence_level" #define GAME_CONFIG_TARGET_HIGHLIGHT_KEY "target_highlight" #define GAME_CONFIG_ITEM_HIGHLIGHT_KEY "item_highlight" #define GAME_CONFIG_COMBAT_LOOKS_KEY "combat_looks" #define GAME_CONFIG_COMBAT_MESSAGES_KEY "combat_messages" #define GAME_CONFIG_COMBAT_TAUNTS_KEY "combat_taunts" #define GAME_CONFIG_LANGUAGE_FILTER_KEY "language_filter" #define GAME_CONFIG_RUNNING_KEY "running" #define GAME_CONFIG_SUBTITLES_KEY "subtitles" #define GAME_CONFIG_COMBAT_SPEED_KEY "combat_speed" #define GAME_CONFIG_PLAYER_SPEED_KEY "player_speed" #define GAME_CONFIG_TEXT_BASE_DELAY_KEY "text_base_delay" #define GAME_CONFIG_TEXT_LINE_DELAY_KEY "text_line_delay" #define GAME_CONFIG_BRIGHTNESS_KEY "brightness" #define GAME_CONFIG_MOUSE_SENSITIVITY_KEY "mouse_sensitivity" #define GAME_CONFIG_INITIALIZE_KEY "initialize" #define GAME_CONFIG_DEVICE_KEY "device" #define GAME_CONFIG_PORT_KEY "port" #define GAME_CONFIG_IRQ_KEY "irq" #define GAME_CONFIG_DMA_KEY "dma" #define GAME_CONFIG_SOUNDS_KEY "sounds" #define GAME_CONFIG_MUSIC_KEY "music" #define GAME_CONFIG_SPEECH_KEY "speech" #define GAME_CONFIG_MASTER_VOLUME_KEY "master_volume" #define GAME_CONFIG_MUSIC_VOLUME_KEY "music_volume" #define GAME_CONFIG_SNDFX_VOLUME_KEY "sndfx_volume" #define GAME_CONFIG_SPEECH_VOLUME_KEY "speech_volume" #define GAME_CONFIG_CACHE_SIZE_KEY "cache_size" #define GAME_CONFIG_MUSIC_PATH1_KEY "music_path1" #define GAME_CONFIG_MUSIC_PATH2_KEY "music_path2" #define GAME_CONFIG_DEBUG_SFXC_KEY "debug_sfxc" #define GAME_CONFIG_MODE_KEY "mode" #define GAME_CONFIG_SHOW_TILE_NUM_KEY "show_tile_num" #define GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY "show_script_messages" #define GAME_CONFIG_SHOW_LOAD_INFO_KEY "show_load_info" #define GAME_CONFIG_OUTPUT_MAP_DATA_INFO_KEY "output_map_data_info" #define GAME_CONFIG_EXECUTABLE_KEY "executable" #define GAME_CONFIG_OVERRIDE_LIBRARIAN_KEY "override_librarian" #define GAME_CONFIG_LIBRARIAN_KEY "librarian" #define GAME_CONFIG_USE_ART_NOT_PROTOS_KEY "use_art_not_protos" #define GAME_CONFIG_REBUILD_PROTOS_KEY "rebuild_protos" #define GAME_CONFIG_FIX_MAP_OBJECTS_KEY "fix_map_objects" #define GAME_CONFIG_FIX_MAP_INVENTORY_KEY "fix_map_inventory" #define GAME_CONFIG_IGNORE_REBUILD_ERRORS_KEY "ignore_rebuild_errors" #define GAME_CONFIG_SHOW_PID_NUMBERS_KEY "show_pid_numbers" #define GAME_CONFIG_SAVE_TEXT_MAPS_KEY "save_text_maps" #define GAME_CONFIG_RUN_MAPPER_AS_GAME_KEY "run_mapper_as_game" #define GAME_CONFIG_DEFAULT_F8_AS_GAME_KEY "default_f8_as_game" #define GAME_CONFIG_SORT_SCRIPT_LIST_KEY "sort_script_list" #define GAME_CONFIG_PLAYER_SPEEDUP_KEY "player_speedup" #define ENGLISH "english" #define FRENCH "french" #define GERMAN "german" #define ITALIAN "italian" #define SPANISH "spanish" typedef enum GameDifficulty { GAME_DIFFICULTY_EASY, GAME_DIFFICULTY_NORMAL, GAME_DIFFICULTY_HARD, } GameDifficulty; typedef enum CombatDifficulty { COMBAT_DIFFICULTY_EASY, COMBAT_DIFFICULTY_NORMAL, COMBAT_DIFFICULTY_HARD, } CombatDifficulty; typedef enum ViolenceLevel { VIOLENCE_LEVEL_NONE, VIOLENCE_LEVEL_MINIMAL, VIOLENCE_LEVEL_NORMAL, VIOLENCE_LEVEL_MAXIMUM_BLOOD, } ViolenceLevel; typedef enum TargetHighlight { TARGET_HIGHLIGHT_OFF, TARGET_HIGHLIGHT_ON, TARGET_HIGHLIGHT_TARGETING_ONLY, } TargetHighlight; extern Config game_config; bool gconfig_init(bool isMapper, int argc, char** argv); bool gconfig_save(); bool gconfig_exit(bool shouldSave); #endif /* FALLOUT_GAME_GCONFIG_H_ */ ================================================ FILE: src/game/gdebug.c ================================================ #include "game/gdebug.h" #include #include #include #include "plib/gnw/debug.h" #include "plib/gnw/gnw.h" // 0x444C90 void fatal_error(const char* format, const char* message, const char* file, int line) { char stringBuffer[260]; debug_printf("\n"); debug_printf(format, message, file, line); win_exit(); printf("\n\n\n\n\n "); printf(format, message, file, line); printf("\n\n\n\n\n"); sprintf(stringBuffer, format, message, file, line); GNWSystemError(stringBuffer); exit(1); } ================================================ FILE: src/game/gdebug.h ================================================ #ifndef FALLOUT_GAME_GDEBUG_H_ #define FALLOUT_GAME_GDEBUG_H_ void fatal_error(const char* format, const char* message, const char* file, int line); #endif /* FALLOUT_GAME_GDEBUG_H_ */ ================================================ FILE: src/game/gdialog.c ================================================ #include "game/gdialog.h" #include #include #include #include "int/window.h" #include "game/actions.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "int/dialog.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gmouse.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/lip_sync.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/textobj.h" #include "game/tile.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" // NOTE: Rare case - as a compatibility measure with Community Edition, this // define is actually an expression as original game used. However leaving it // in the code produces to too many diffs in this file, which is not good for // RE<->CE reconciliation. #define GAME_DIALOG_WINDOW_WIDTH (scr_size.lrx - scr_size.ulx + 1) #define GAME_DIALOG_WINDOW_HEIGHT 480 #define GAME_DIALOG_REPLY_WINDOW_X 135 #define GAME_DIALOG_REPLY_WINDOW_Y 225 #define GAME_DIALOG_REPLY_WINDOW_WIDTH 379 #define GAME_DIALOG_REPLY_WINDOW_HEIGHT 58 #define GAME_DIALOG_OPTIONS_WINDOW_X 127 #define GAME_DIALOG_OPTIONS_WINDOW_Y 335 #define GAME_DIALOG_OPTIONS_WINDOW_WIDTH 393 #define GAME_DIALOG_OPTIONS_WINDOW_HEIGHT 117 // NOTE: See `GAME_DIALOG_WINDOW_WIDTH`. #define GAME_DIALOG_REVIEW_WINDOW_WIDTH (scr_size.lrx - scr_size.ulx + 1) #define GAME_DIALOG_REVIEW_WINDOW_HEIGHT 480 #define DIALOG_REVIEW_ENTRIES_CAPACITY 80 #define DIALOG_OPTION_ENTRIES_CAPACITY 30 typedef enum GameDialogReviewWindowButton { GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP, GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN, GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE, GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT, } GameDialogReviewWindowButton; typedef enum GameDialogReviewWindowButtonFrm { GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_NORMAL, GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_PRESSED, GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_NORMAL, GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_PRESSED, GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_NORMAL, GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_PRESSED, GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT, } GameDialogReviewWindowButtonFrm; typedef enum GameDialogReaction { GAME_DIALOG_REACTION_GOOD = 49, GAME_DIALOG_REACTION_NEUTRAL = 50, GAME_DIALOG_REACTION_BAD = 51, } GameDialogReaction; typedef struct GameDialogReviewEntry { int replyMessageListId; int replyMessageId; // Can be NULL. char* replyText; int optionMessageListId; int optionMessageId; char* optionText; } GameDialogReviewEntry; typedef struct GameDialogOptionEntry { int messageListId; int messageId; int reaction; int proc; int btn; int field_14; char text[900]; int field_39C; } GameDialogOptionEntry; typedef struct GameDialogBlock { Program* program; int replyMessageListId; int replyMessageId; int offset; // NOTE: The is something odd about next two members. There are 2700 bytes, // which is 3 x 900, but anywhere in the app only 900 characters is used. // The length of text in [DialogOptionEntry] is definitely 900 bytes. There // are two possible explanations: // - it's an array of 3 elements. // - there are three separate elements, two of which are not used, therefore // they are not referenced anywhere, but they take up their space. // // See `gdProcessChoice` for more info how this unreferenced range plays // important role. char replyText[900]; char field_394[1800]; GameDialogOptionEntry options[DIALOG_OPTION_ENTRIES_CAPACITY]; } GameDialogBlock; // Provides button configuration for party member combat control and // customization interface. typedef struct GameDialogButtonData { int x; int y; int upFrmId; int downFrmId; int disabledFrmId; CacheEntry* upFrmHandle; CacheEntry* downFrmHandle; CacheEntry* disabledFrmHandle; int keyCode; int value; } GameDialogButtonData; typedef struct STRUCT_5189E4 { int messageId; int value; } STRUCT_5189E4; typedef enum PartyMemberCustomizationOption { PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE, PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE, PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON, PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE, PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO, PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE, PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT, } PartyMemberCustomizationOption; static int gdHide(); static int gdUnhide(); static int gdUnhideReply(); static int gdAddOption(int a1, int a2, int a3); static int gdAddOptionStr(int a1, const char* a2, int a3); static int gdReviewInit(int* win); static int gdReviewExit(int* win); static int gdReview(); static void gdReviewPressed(int btn, int keyCode); static void gdReviewDisplay(int win, int origin); static void gdReviewFree(); static int gdAddReviewReply(int messageListId, int messageId); static int gdAddReviewReplyStr(const char* text); static int gdAddReviewOptionChosen(int messageListId, int messageId); static int gdAddReviewOptionChosenStr(const char* text); static int gdProcessInit(); static void gdProcessCleanup(); static int gdProcessExit(); static void gdUpdateMula(); static int gdProcess(); static int gdProcessChoice(int a1); static void gdProcessHighlight(int a1); static void gdProcessUnHighlight(int a1); static void gdProcessReply(); static void gdProcessUpdate(); static int gdCreateHeadWindow(); static void gdDestroyHeadWindow(); static void gdSetupFidget(int headFid, int reaction); static void gdWaitForFidget(); static void gdPlayTransition(int a1); static void reply_arrow_up(int btn, int a2); static void reply_arrow_down(int btn, int a2); static void reply_arrow_restore(int btn, int a2); static void demo_copy_title(int win); static void demo_copy_options(int win); static void gDialogRefreshOptionsRect(int win, Rect* drawRect); static void gdialog_bk(); static void gdialog_scroll_subwin(int a1, int a2, unsigned char* a3, unsigned char* a4, unsigned char* a5, int a6, int a7); static int text_num_lines(const char* a1, int a2); static int text_to_rect_wrapped(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color); static int text_to_rect_func(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color, int a7); static int gdialog_barter_create_win(); static void gdialog_barter_destroy_win(); static void gdialog_barter_cleanup_tables(); static int gdControlCreateWin(); static void gdControlDestroyWin(); static void gdControlUpdateInfo(); static void gdControlPressed(int a1, int a2); static int gdPickAIUpdateMsg(Object* obj); static int gdCanBarter(); static void gdControl(); static int gdCustomCreateWin(); static void gdCustomDestroyWin(); static void gdCustom(); static void gdCustomUpdateInfo(); static void gdCustomSelectRedraw(unsigned char* dest, int pitch, int type, int selectedIndex); static int gdCustomSelect(int a1); static void gdCustomUpdateSetting(int option, int value); static void gdialog_barter_pressed(int btn, int a2); static int gdialog_window_create(); static void gdialog_window_destroy(); static int talk_to_create_background_window(); static int talk_to_refresh_background_window(); static int talkToRefreshDialogWindowRect(Rect* rect); static void talk_to_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int x, int y, int destPitch, unsigned char* a9, unsigned char* a10); static void gdDisplayFrame(Art* art, int frame); static void gdBlendTableInit(); static void gdBlendTableExit(); // 0x5186D4 static int dialog_state_fix = 0; // 0x5186D8 static int gdNumOptions = 0; // 0x5186DC static int curReviewSlot = 0; // 0x5186E0 static unsigned char* headWindowBuffer = NULL; // 0x5186E4 static int gReplyWin = -1; // 0x5186E8 static int gOptionWin = -1; // 0x5186EC static bool gdialog_window_created = false; // 0x5186F0 static int boxesWereDisabled = 0; // 0x5186F4 static int fidgetFID = 0; // 0x5186F8 static CacheEntry* fidgetKey = NULL; // 0x5186FC static Art* fidgetFp = NULL; // 0x518700 static int backgroundIndex = 2; // 0x518704 static int lipsFID = 0; // 0x518708 static CacheEntry* lipsKey = NULL; // 0x51870C static Art* lipsFp = NULL; // 0x518710 static bool gdialog_speech_playing = false; // 0x518714 static int dialogue_state = 0; // 0x518718 static int dialogue_switch_mode = 0; // 0x51871C static int gdialog_state = -1; // 0x518720 static bool gdDialogWentOff = false; // 0x518724 static bool gdDialogTurnMouseOff = false; // 0x518728 static int gdReenterLevel = 0; // 0x51872C static bool gdReplyTooBig = false; // 0x518730 static Object* peon_table_obj = NULL; // 0x518734 static Object* barterer_table_obj = NULL; // 0x518738 static Object* barterer_temp_obj = NULL; // 0x51873C static int gdBarterMod = 0; // 0x518740 static int dialogueBackWindow = -1; // 0x518744 static int dialogueWindow = -1; // 0x518748 static Rect backgrndRects[8] = { { 126, 14, 152, 40 }, { 488, 14, 514, 40 }, { 126, 188, 152, 214 }, { 488, 188, 514, 214 }, { 152, 14, 488, 24 }, { 152, 204, 488, 214 }, { 126, 40, 136, 188 }, { 504, 40, 514, 188 }, }; // 0x5187C8 static int talk_need_to_center = 1; // 0x5187CC static bool can_start_new_fidget = false; // 0x5187D0 static int gd_replyWin = -1; // 0x5187D4 static int gd_optionsWin = -1; // 0x5187D8 static int gDialogMusicVol = -1; // 0x5187DC static int gdCenterTile = -1; // 0x5187E0 static int gdPlayerTile = -1; // 0x5187E4 unsigned char* light_BlendTable = NULL; // 0x5187E8 unsigned char* dark_BlendTable = NULL; // 0x5187EC static int dialogue_just_started = 0; // 0x5187F0 static int dialogue_seconds_since_last_input = 0; // 0x5187F4 static CacheEntry* reviewKeys[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT] = { INVALID_CACHE_ENTRY, INVALID_CACHE_ENTRY, INVALID_CACHE_ENTRY, INVALID_CACHE_ENTRY, INVALID_CACHE_ENTRY, INVALID_CACHE_ENTRY, }; // 0x51880C static CacheEntry* reviewBackKey = INVALID_CACHE_ENTRY; // 0x518810 static CacheEntry* reviewDispBackKey = INVALID_CACHE_ENTRY; // 0x518814 static unsigned char* reviewDispBuf = NULL; // 0x518818 static int reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT] = { 35, 35, 82, }; // 0x518824 static int reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_COUNT] = { 35, 37, 46, }; // 0x518830 static int reviewFids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT] = { 89, // di_bgdn1.frm - dialog big down arrow 90, // di_bgdn2.frm - dialog big down arrow 87, // di_bgup1.frm - dialog big up arrow 88, // di_bgup2.frm - dialog big up arrow 91, // di_done1.frm - dialog big done button up 92, // di_done2.frm - dialog big done button down }; // 0x518848 Object* dialog_target = NULL; // 0x51884C bool dialog_target_is_party = false; // 0x518850 int dialogue_head = 0; // 0x518854 int dialogue_scr_id = -1; // Maps phoneme to talking head frame. // // 0x518858 static int head_phoneme_lookup[PHONEME_COUNT] = { 0, 3, 1, 1, 3, 1, 1, 1, 7, 8, 7, 3, 1, 8, 1, 7, 7, 6, 6, 2, 2, 2, 2, 4, 4, 5, 5, 2, 2, 2, 2, 2, 6, 2, 2, 5, 8, 2, 2, 2, 2, 8, }; // 0x51890C static const char* react_strs[3] = { "Said Good", "Said Neutral", "Said Bad", }; // 0x518918 static int dialogue_subwin_len = 0; // 0x51891C static GameDialogButtonData control_button_info[5] = { { 438, 37, 397, 395, 396, NULL, NULL, NULL, 2098, 4 }, { 438, 67, 394, 392, 393, NULL, NULL, NULL, 2103, 3 }, { 438, 96, 406, 404, 405, NULL, NULL, NULL, 2102, 2 }, { 438, 126, 400, 398, 399, NULL, NULL, NULL, 2111, 1 }, { 438, 156, 403, 401, 402, NULL, NULL, NULL, 2099, 0 }, }; // 0x5189E4 static STRUCT_5189E4 custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT][6] = { { { 100, AREA_ATTACK_MODE_ALWAYS }, // Always! { 101, AREA_ATTACK_MODE_SOMETIMES }, // Sometimes, don't worry about hitting me { 102, AREA_ATTACK_MODE_BE_SURE }, // Be sure you won't hit me { 103, AREA_ATTACK_MODE_BE_CAREFUL }, // Be careful not to hit me { 104, AREA_ATTACK_MODE_BE_ABSOLUTELY_SURE }, // Be absolutely sure you won't hit me { -1, 0 }, }, { { 200, RUN_AWAY_MODE_COWARD - 1 }, // Abject coward { 201, RUN_AWAY_MODE_FINGER_HURTS - 1 }, // Your finger hurts { 202, RUN_AWAY_MODE_BLEEDING - 1 }, // You're bleeding a bit { 203, RUN_AWAY_MODE_NOT_FEELING_GOOD - 1 }, // Not feeling good { 204, RUN_AWAY_MODE_TOURNIQUET - 1 }, // You need a tourniquet { 205, RUN_AWAY_MODE_NEVER - 1 }, // Never! }, { { 300, BEST_WEAPON_NO_PREF }, // None { 301, BEST_WEAPON_MELEE }, // Melee { 302, BEST_WEAPON_MELEE_OVER_RANGED }, // Melee then ranged { 303, BEST_WEAPON_RANGED_OVER_MELEE }, // Ranged then melee { 304, BEST_WEAPON_RANGED }, // Ranged { 305, BEST_WEAPON_UNARMED }, // Unarmed }, { { 400, DISTANCE_STAY_CLOSE }, // Stay close to me { 401, DISTANCE_CHARGE }, // Charge! { 402, DISTANCE_SNIPE }, // Snipe the enemy { 403, DISTANCE_ON_YOUR_OWN }, // On your own { 404, DISTANCE_STAY }, // Say where you are { -1, 0 }, }, { { 500, ATTACK_WHO_WHOMEVER_ATTACKING_ME }, // Whomever is attacking me { 501, ATTACK_WHO_STRONGEST }, // The strongest { 502, ATTACK_WHO_WEAKEST }, // The weakest { 503, ATTACK_WHO_WHOMEVER }, // Whomever you want { 504, ATTACK_WHO_CLOSEST }, // Whoever is closest { -1, 0 }, }, { { 600, CHEM_USE_CLEAN }, // I'm clean { 601, CHEM_USE_STIMS_WHEN_HURT_LITTLE }, // Stimpacks when hurt a bit { 602, CHEM_USE_STIMS_WHEN_HURT_LOTS }, // Stimpacks when hurt a lot { 603, CHEM_USE_SOMETIMES }, // Any drug some of the time { 604, CHEM_USE_ANYTIME }, // Any drug any time { -1, 0 }, }, }; // 0x518B04 static GameDialogButtonData custom_button_info[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT] = { { 95, 9, 410, 409, -1, NULL, NULL, NULL, 0, 0 }, { 96, 38, 416, 415, -1, NULL, NULL, NULL, 1, 0 }, { 96, 68, 418, 417, -1, NULL, NULL, NULL, 2, 0 }, { 96, 98, 414, 413, -1, NULL, NULL, NULL, 3, 0 }, { 96, 127, 408, 407, -1, NULL, NULL, NULL, 4, 0 }, { 96, 157, 412, 411, -1, NULL, NULL, NULL, 5, 0 }, }; // 0x58EA80 static int custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT]; // custom.msg // // 0x58EA98 static MessageList custom_msg_file; // 0x58EAA0 unsigned char light_GrayTable[256]; // 0x58EBA0 unsigned char dark_GrayTable[256]; // 0x58ECA0 static unsigned char* backgrndBufs[8]; // 0x58ECC0 static Rect optionRect; // 0x58ECD0 static Rect replyRect; // 0x58ECE0 static GameDialogReviewEntry reviewList[DIALOG_REVIEW_ENTRIES_CAPACITY]; // 0x58F460 static int custom_buttons_start; // 0x58F464 static int control_buttons_start; // 0x58F468 static int reviewOldFont; // 0x58F46C static CacheEntry* dialog_red_button_up_key; // 0x58F470 static int gdialog_buttons[9]; // 0x58F494 static CacheEntry* upper_hi_key; // 0x58F498 static CacheEntry* gdialog_review_up_key; // 0x58F49C static int lower_hi_len; // 0x58F4A0 static CacheEntry* gdialog_review_down_key; // 0x58F4A4 static unsigned char* dialog_red_button_down_buf; // 0x58F4A8 static int lower_hi_wid; // 0x58F4AC static unsigned char* dialog_red_button_up_buf; // 0x58F4B0 static int upper_hi_wid; // Yellow highlight blick effect. // // 0x58F4B4 static Art* lower_hi_fp; // 0x58F4B8 static int upper_hi_len; // 0x58F4BC static CacheEntry* dialog_red_button_down_key; // 0x58F4C0 static CacheEntry* lower_hi_key; // White highlight blick effect. // // This effect appears at the top-right corner on dialog display. Together with // [gDialogLowerHighlight] it gives an effect of depth of the monitor. // // 0x58F4C4 static Art* upper_hi_fp; // 0x58F4C8 static int oldFont; // 0x58F4CC static unsigned int fidgetLastTime; // 0x58F4D0 static int fidgetAnim; // 0x58F4D4 static GameDialogBlock dialogBlock; // 0x596C30 static int talkOldFont; // 0x596C34 static unsigned int fidgetTocksPerFrame; // 0x596C38 static int fidgetFrameCounter; // 0x444D1C int gdialogInit() { return 0; } // 0x444D20 int gdialogReset() { gdialogFreeSpeech(); return 0; } // 0x444D20. int gdialogExit() { gdialogFreeSpeech(); return 0; } // 0x444D2C bool gdialogActive() { return dialog_state_fix != 0; } // gdialogEnter // 0x444D3C void gdialogEnter(Object* a1, int a2) { if (a1 == NULL) { debug_printf("\nError: gdialogEnter: target was NULL!"); return; } gdDialogWentOff = false; if (isInCombat()) { return; } if (a1->sid == -1) { return; } if (PID_TYPE(a1->pid) != OBJ_TYPE_ITEM && SID_TYPE(a1->sid) != SCRIPT_TYPE_SPATIAL) { MessageListItem messageListItem; int rc = action_can_talk_to(obj_dude, a1); if (rc == -1) { // You can't see there. messageListItem.num = 660; if (message_search(&proto_main_msg_file, &messageListItem)) { if (a2) { display_print(messageListItem.text); } else { debug_printf(messageListItem.text); } } else { debug_printf("\nError: gdialog: Can't find message!"); } return; } if (rc == -2) { // Too far away. messageListItem.num = 661; if (message_search(&proto_main_msg_file, &messageListItem)) { if (a2) { display_print(messageListItem.text); } else { debug_printf(messageListItem.text); } } else { debug_printf("\nError: gdialog: Can't find message!"); } return; } } gdCenterTile = tile_center_tile; gdBarterMod = 0; gdPlayerTile = obj_dude->tile; map_disable_bk_processes(); dialog_state_fix = 1; dialog_target = a1; dialog_target_is_party = isPartyMember(a1); dialogue_just_started = 1; if (a1->sid != -1) { exec_script_proc(a1->sid, SCRIPT_PROC_TALK); } Script* script; if (scr_ptr(a1->sid, &script) == -1) { gmouse_3d_on(); map_enable_bk_processes(); scr_exec_map_update_scripts(); dialog_state_fix = 0; return; } if (script->scriptOverrides || dialogue_state != 4) { dialogue_just_started = 0; map_enable_bk_processes(); scr_exec_map_update_scripts(); dialog_state_fix = 0; return; } gdialogFreeSpeech(); if (gdialog_state == 1) { // TODO: Not sure about these conditions. if (dialogue_switch_mode == 2) { gdialog_window_destroy(); } else if (dialogue_switch_mode == 8) { gdialog_window_destroy(); } else if (dialogue_switch_mode == 11) { gdialog_window_destroy(); } else { if (dialogue_switch_mode == gdialog_state) { gdialog_barter_destroy_win(); } else if (dialogue_state == gdialog_state) { gdialog_window_destroy(); } else if (dialogue_state == a2) { gdialog_barter_destroy_win(); } } gdialogExitFromScript(); } gdialog_state = 0; dialogue_state = 0; int tile = obj_dude->tile; if (gdPlayerTile != tile) { gdCenterTile = tile; } if (gdDialogWentOff) { tile_scroll_to(gdCenterTile, 2); } map_enable_bk_processes(); scr_exec_map_update_scripts(); dialog_state_fix = 0; } // 0x444FE4 void gdialogSystemEnter() { game_state_update(); gdDialogTurnMouseOff = true; soundContinueAll(); gdialogEnter(dialog_target, 0); soundContinueAll(); if (gdPlayerTile != obj_dude->tile) { gdCenterTile = obj_dude->tile; } if (gdDialogWentOff) { tile_scroll_to(gdCenterTile, 2); } game_state_request(GAME_STATE_2); game_state_update(); } // 0x445050 void gdialogSetupSpeech(const char* audioFileName) { if (audioFileName == NULL) { debug_printf("\nGDialog: Bleep!"); gsound_play_sfx_file("censor"); return; } char name[16]; if (art_get_base_name(OBJ_TYPE_HEAD, dialogue_head & 0xFFF, name) == -1) { return; } if (lips_load_file(audioFileName, name) == -1) { return; } gdialog_speech_playing = true; lips_play_speech(); debug_printf("Starting lipsynch speech"); } // 0x4450C4 void gdialogFreeSpeech() { if (gdialog_speech_playing) { debug_printf("Ending lipsynch system"); gdialog_speech_playing = false; lips_free_speech(); } } // 0x4450EC int gdialogEnableBK() { add_bk_process(gdialog_bk); return 0; } // 0x4450FC int gdialogDisableBK() { remove_bk_process(gdialog_bk); return 0; } // 0x44510C int gdialogInitFromScript(int headFid, int reaction) { if (dialogue_state == 1) { return -1; } if (gdialog_state == 1) { return 0; } anim_stop(); boxesWereDisabled = disable_box_bar_win(); dialog_target_is_party = isPartyMember(dialog_target); oldFont = text_curr(); text_font(101); dialogSetReplyWindow(135, 225, 379, 58, NULL); dialogSetReplyColor(0.3f, 0.3f, 0.3f); dialogSetOptionWindow(127, 335, 393, 117, NULL); dialogSetOptionColor(0.2f, 0.2f, 0.2f); dialogTitle(NULL); dialogRegisterWinDrawCallbacks(demo_copy_title, demo_copy_options); gdBlendTableInit(); cycle_disable(); if (gdDialogTurnMouseOff) { gmouse_disable(0); } gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); text_object_reset(); if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_ITEM) { tile_scroll_to(dialog_target->tile, 2); } talk_need_to_center = 1; gdCreateHeadWindow(); add_bk_process(gdialog_bk); gdSetupFidget(headFid, reaction); gdialog_state = 1; gmouse_disable_scrolling(); if (headFid == -1) { gDialogMusicVol = gsound_background_volume_get_set(gDialogMusicVol / 2); } else { gDialogMusicVol = -1; gsound_background_stop(); } gdDialogWentOff = true; return 0; } // 0x445298 int gdialogExitFromScript() { if (dialogue_switch_mode == 2 || dialogue_switch_mode == 8 || dialogue_switch_mode == 11) { return -1; } if (gdialog_state == 0) { return 0; } gdialogFreeSpeech(); gdReviewFree(); remove_bk_process(gdialog_bk); if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_ITEM) { if (gdPlayerTile != obj_dude->tile) { gdCenterTile = obj_dude->tile; } tile_scroll_to(gdCenterTile, 2); } gdDestroyHeadWindow(); text_font(oldFont); if (fidgetFp != NULL) { art_ptr_unlock(fidgetKey); fidgetFp = NULL; } if (lipsKey != NULL) { if (art_ptr_unlock(lipsKey) == -1) { debug_printf("Failure unlocking lips frame!\n"); } lipsKey = NULL; lipsFp = NULL; lipsFID = 0; } // NOTE: Uninline. gdBlendTableExit(); gdialog_state = 0; dialogue_state = 0; cycle_enable(); if (!game_ui_is_disabled()) { gmouse_enable_scrolling(); } if (gDialogMusicVol == -1) { gsound_background_restart_last(11); } else { gsound_background_volume_set(gDialogMusicVol); } if (boxesWereDisabled) { enable_box_bar_win(); } boxesWereDisabled = 0; if (gdDialogTurnMouseOff) { if (!game_ui_is_disabled()) { gmouse_enable(); } gdDialogTurnMouseOff = 0; } if (!game_ui_is_disabled()) { gmouse_3d_on(); } gdDialogWentOff = true; return 0; } // 0x445438 void gdialogSetBackground(int a1) { if (a1 != -1) { backgroundIndex = a1; } } // Renders supplementary message in reply area of the dialog. // // 0x445448 void gdialogDisplayMsg(char* msg) { if (gd_replyWin == -1) { debug_printf("\nError: Reply window doesn't exist!"); return; } replyRect.ulx = 5; replyRect.uly = 10; replyRect.lrx = 374; replyRect.lry = 58; demo_copy_title(gReplyWin); unsigned char* windowBuffer = win_get_buf(gReplyWin); int lineHeight = text_height(); int a4 = 0; // NOTE: Uninline. text_to_rect_wrapped(windowBuffer, &replyRect, msg, &a4, lineHeight, 379, colorTable[992] | 0x2000000); win_show(gd_replyWin); win_draw(gReplyWin); } // 0x4454FC int gdialogStart() { curReviewSlot = 0; gdNumOptions = 0; return 0; } // 0x445510 int gdialogSayMessage() { mouse_show(); gdialogGo(); gdNumOptions = 0; dialogBlock.replyMessageListId = -1; return 0; } // NOTE: If you look at the scripts handlers, my best guess that their intention // was to allow scripters to specify proc names instead of proc addresses. They // dropped this idea, probably because they've updated their own compiler, or // maybe there was not enough time to complete it. Any way, [procedure] is the // identifier of the procedure in the script, but it is silently ignored. // // 0x445538 int gdialogOption(int messageListId, int messageId, const char* proc, int reaction) { dialogBlock.options[gdNumOptions].proc = 0; return gdAddOption(messageListId, messageId, reaction); } // NOTE: If you look at the script handlers, my best guess that their intention // was to allow scripters to specify proc names instead of proc addresses. They // dropped this idea, probably because they've updated their own compiler, or // maybe there was not enough time to complete it. Any way, [procedure] is the // identifier of the procedure in the script, but it is silently ignored. // // 0x445578 int gdialogOptionStr(int messageListId, const char* text, const char* proc, int reaction) { dialogBlock.options[gdNumOptions].proc = 0; return gdAddOptionStr(messageListId, text, reaction); } // 0x4455B8 int gdialogOptionProc(int messageListId, int messageId, int proc, int reaction) { dialogBlock.options[gdNumOptions].proc = proc; return gdAddOption(messageListId, messageId, reaction); } // 0x4455FC int gdialogOptionProcStr(int messageListId, const char* text, int proc, int reaction) { dialogBlock.options[gdNumOptions].proc = proc; return gdAddOptionStr(messageListId, text, reaction); } // 0x445640 int gdialogReply(Program* program, int messageListId, int messageId) { gdAddReviewReply(messageListId, messageId); dialogBlock.program = program; dialogBlock.replyMessageListId = messageListId; dialogBlock.replyMessageId = messageId; dialogBlock.offset = 0; dialogBlock.replyText[0] = '\0'; gdNumOptions = 0; return 0; } // 0x44567C int gdialogReplyStr(Program* program, int messageListId, const char* text) { gdAddReviewReplyStr(text); dialogBlock.program = program; dialogBlock.offset = 0; dialogBlock.replyMessageListId = -4; dialogBlock.replyMessageId = -4; strcpy(dialogBlock.replyText, text); gdNumOptions = 0; return 0; } // 0x4456D8 int gdialogGo() { if (dialogBlock.replyMessageListId == -1) { return 0; } int rc = 0; if (gdNumOptions < 1) { dialogBlock.options[gdNumOptions].proc = 0; if (gdAddOption(-1, -1, 50) == -1) { interpretError("Error setting option."); rc = -1; } } if (rc != -1) { rc = gdProcess(); } gdNumOptions = 0; return rc; } // 0x445764 void gdialogUpdatePartyStatus() { if (dialogue_state != 1) { return; } bool is_party = isPartyMember(dialog_target); if (is_party == dialog_target_is_party) { return; } // NOTE: Uninline. gdHide(); gdialog_window_destroy(); dialog_target_is_party = is_party; gdialog_window_create(); // NOTE: Uninline. gdUnhide(); } // NOTE: Inlined. // // 0x4457EC static int gdHide() { if (gd_replyWin != -1) { win_hide(gd_replyWin); } if (gd_optionsWin != -1) { win_hide(gd_optionsWin); } return 0; } // NOTE: Inlined. // // 0x445818 static int gdUnhide() { if (gd_replyWin != -1) { win_show(gd_replyWin); } if (gd_optionsWin != -1) { win_show(gd_optionsWin); } return 0; } // NOTE: Unused. // // 0x445844 static int gdUnhideReply() { if (gd_replyWin != -1) { win_show(gd_replyWin); } return 0; } // 0x44585C static int gdAddOption(int messageListId, int messageId, int reaction) { if (gdNumOptions >= DIALOG_OPTION_ENTRIES_CAPACITY) { debug_printf("\nError: dialog: Ran out of options!"); return -1; } GameDialogOptionEntry* optionEntry = &(dialogBlock.options[gdNumOptions]); optionEntry->messageListId = messageListId; optionEntry->messageId = messageId; optionEntry->reaction = reaction; optionEntry->btn = -1; optionEntry->text[0] = '\0'; gdNumOptions++; return 0; } // 0x4458BC static int gdAddOptionStr(int messageListId, const char* text, int reaction) { if (gdNumOptions >= DIALOG_OPTION_ENTRIES_CAPACITY) { debug_printf("\nError: dialog: Ran out of options!"); return -1; } GameDialogOptionEntry* optionEntry = &(dialogBlock.options[gdNumOptions]); optionEntry->messageListId = -4; optionEntry->messageId = -4; optionEntry->reaction = reaction; optionEntry->btn = -1; sprintf(optionEntry->text, "%c %s", '\x95', text); gdNumOptions++; return 0; } // 0x445938 static int gdReviewInit(int* win) { if (gdialog_speech_playing) { if (soundPlaying(lip_info.sound)) { gdialogFreeSpeech(); } } reviewOldFont = text_curr(); if (win == NULL) { return -1; } int reviewWindowX = 0; int reviewWindowY = 0; *win = win_add(reviewWindowX, reviewWindowY, GAME_DIALOG_REVIEW_WINDOW_WIDTH, GAME_DIALOG_REVIEW_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (*win == -1) { return -1; } int fid = art_id(OBJ_TYPE_INTERFACE, 102, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &reviewBackKey); if (backgroundFrmData == NULL) { win_delete(*win); *win = -1; return -1; } unsigned char* windowBuffer = win_get_buf(*win); buf_to_buf(backgroundFrmData, GAME_DIALOG_REVIEW_WINDOW_WIDTH, GAME_DIALOG_REVIEW_WINDOW_HEIGHT, GAME_DIALOG_REVIEW_WINDOW_WIDTH, windowBuffer, GAME_DIALOG_REVIEW_WINDOW_WIDTH); art_ptr_unlock(reviewBackKey); reviewBackKey = INVALID_CACHE_ENTRY; unsigned char* buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT]; int index; for (index = 0; index < GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, reviewFids[index], 0, 0, 0); buttonFrmData[index] = art_ptr_lock_data(fid, 0, 0, &(reviewKeys[index])); if (buttonFrmData[index] == NULL) { break; } } if (index != GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT) { gdReviewExit(win); return -1; } int upBtn = win_register_button(*win, 475, 152, reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP], reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_UP], -1, -1, -1, KEY_ARROW_UP, buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_NORMAL], buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_UP_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (upBtn == -1) { gdReviewExit(win); return -1; } win_register_button_sound_func(upBtn, gsound_med_butt_press, gsound_med_butt_release); int downBtn = win_register_button(*win, 475, 191, reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN], reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_SCROLL_DOWN], -1, -1, -1, KEY_ARROW_DOWN, buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_NORMAL], buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_ARROW_DOWN_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (downBtn == -1) { gdReviewExit(win); return -1; } win_register_button_sound_func(downBtn, gsound_med_butt_press, gsound_med_butt_release); int doneBtn = win_register_button(*win, 499, 398, reviewFidWids[GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE], reviewFidLens[GAME_DIALOG_REVIEW_WINDOW_BUTTON_DONE], -1, -1, -1, KEY_ESCAPE, buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_NORMAL], buttonFrmData[GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_DONE_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn == -1) { gdReviewExit(win); return -1; } win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); text_font(101); win_draw(*win); remove_bk_process(gdialog_bk); int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 102, 0, 0, 0); reviewDispBuf = art_ptr_lock_data(backgroundFid, 0, 0, &reviewDispBackKey); if (reviewDispBuf == NULL) { gdReviewExit(win); return -1; } return 0; } // 0x445C18 static int gdReviewExit(int* win) { add_bk_process(gdialog_bk); for (int index = 0; index < GAME_DIALOG_REVIEW_WINDOW_BUTTON_FRM_COUNT; index++) { if (reviewKeys[index] != INVALID_CACHE_ENTRY) { art_ptr_unlock(reviewKeys[index]); reviewKeys[index] = INVALID_CACHE_ENTRY; } } if (reviewDispBackKey != INVALID_CACHE_ENTRY) { art_ptr_unlock(reviewDispBackKey); reviewDispBackKey = INVALID_CACHE_ENTRY; reviewDispBuf = NULL; } text_font(reviewOldFont); if (win == NULL) { return -1; } win_delete(*win); *win = -1; return 0; } // 0x445CA0 static int gdReview() { int win; if (gdReviewInit(&win) == -1) { debug_printf("\nError initializing review window!"); return -1; } // probably current top line or something like this, which is used to scroll int v1 = 0; gdReviewDisplay(win, v1); while (true) { int keyCode = get_input(); if (keyCode == 17 || keyCode == 24 || keyCode == 324) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0 || keyCode == KEY_ESCAPE) { break; } // likely scrolling if (keyCode == 328) { v1 -= 1; if (v1 >= 0) { gdReviewDisplay(win, v1); } else { v1 = 0; } } else if (keyCode == 336) { v1 += 1; if (v1 <= curReviewSlot - 1) { gdReviewDisplay(win, v1); } else { v1 = curReviewSlot - 1; } } } if (gdReviewExit(&win) == -1) { return -1; } return 0; } // NOTE: Uncollapsed 0x445CA0 with different signature. static void gdReviewPressed(int btn, int keyCode) { gdReview(); } // 0x445D44 static void gdReviewDisplay(int win, int origin) { Rect entriesRect; entriesRect.ulx = 113; entriesRect.uly = 76; entriesRect.lrx = 422; entriesRect.lry = 418; int v20 = text_height() + 2; unsigned char* windowBuffer = win_get_buf(win); if (windowBuffer == NULL) { debug_printf("\nError: gdialog: review: can't find buffer!"); return; } int width = GAME_DIALOG_WINDOW_WIDTH; buf_to_buf( reviewDispBuf + width * entriesRect.uly + entriesRect.ulx, width, entriesRect.lry - entriesRect.uly + 15, width, windowBuffer + width * entriesRect.uly + entriesRect.ulx, width); int y = 76; for (int index = origin; index < curReviewSlot; index++) { GameDialogReviewEntry* dialogReviewEntry = &(reviewList[index]); char name[60]; sprintf(name, "%s:", object_name(dialog_target)); win_print(win, name, 180, 88, y, colorTable[992] | 0x2000000); entriesRect.uly += v20; char* replyText; if (dialogReviewEntry->replyMessageListId <= -3) { replyText = dialogReviewEntry->replyText; } else { replyText = scr_get_msg_str(dialogReviewEntry->replyMessageListId, dialogReviewEntry->replyMessageId); } if (replyText == NULL) { GNWSystemError("\nGDialog::Error Grabbing text message!"); exit(1); } // NOTE: Uninline. y = text_to_rect_wrapped(windowBuffer + 113, &entriesRect, replyText, NULL, text_height(), 640, colorTable[768] | 0x2000000); if (dialogReviewEntry->optionMessageListId != -3) { sprintf(name, "%s:", object_name(obj_dude)); win_print(win, name, 180, 88, y, colorTable[21140] | 0x2000000); entriesRect.uly += v20; char* optionText; if (dialogReviewEntry->optionMessageListId <= -3) { optionText = dialogReviewEntry->optionText; } else { optionText = scr_get_msg_str(dialogReviewEntry->optionMessageListId, dialogReviewEntry->optionMessageId); } if (optionText == NULL) { GNWSystemError("\nGDialog::Error Grabbing text message!"); exit(1); } // NOTE: Uninline. y = text_to_rect_wrapped(windowBuffer + 113, &entriesRect, optionText, NULL, text_height(), 640, colorTable[15855] | 0x2000000); } if (y >= 407) { break; } } entriesRect.ulx = 88; entriesRect.uly = 76; entriesRect.lry += 14; entriesRect.lrx = 434; win_draw_rect(win, &entriesRect); } // 0x445FDC static void gdReviewFree() { for (int index = 0; index < curReviewSlot; index++) { GameDialogReviewEntry* entry = &(reviewList[index]); entry->replyMessageListId = 0; entry->replyMessageId = 0; if (entry->replyText != NULL) { mem_free(entry->replyText); entry->replyText = NULL; } entry->optionMessageListId = 0; entry->optionMessageId = 0; } } // 0x446040 static int gdAddReviewReply(int messageListId, int messageId) { if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) { debug_printf("\nError: Ran out of review slots!"); return -1; } GameDialogReviewEntry* entry = &(reviewList[curReviewSlot]); entry->replyMessageListId = messageListId; entry->replyMessageId = messageId; // NOTE: I'm not sure why there are two consequtive assignments. entry->optionMessageListId = -1; entry->optionMessageId = -1; entry->optionMessageListId = -3; entry->optionMessageId = -3; curReviewSlot++; return 0; } // 0x4460B4 static int gdAddReviewReplyStr(const char* string) { if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) { debug_printf("\nError: Ran out of review slots!"); return -1; } GameDialogReviewEntry* entry = &(reviewList[curReviewSlot]); entry->replyMessageListId = -4; entry->replyMessageId = -4; if (entry->replyText != NULL) { mem_free(entry->replyText); entry->replyText = NULL; } entry->replyText = (char*)mem_malloc(strlen(string) + 1); strcpy(entry->replyText, string); entry->optionMessageListId = -3; entry->optionMessageId = -3; entry->optionText = NULL; curReviewSlot++; return 0; } // 0x4461A4 static int gdAddReviewOptionChosen(int messageListId, int messageId) { if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) { debug_printf("\nError: Ran out of review slots!"); return -1; } GameDialogReviewEntry* entry = &(reviewList[curReviewSlot - 1]); entry->optionMessageListId = messageListId; entry->optionMessageId = messageId; entry->optionText = NULL; return 0; } // 0x4461F0 static int gdAddReviewOptionChosenStr(const char* string) { if (curReviewSlot >= DIALOG_REVIEW_ENTRIES_CAPACITY) { debug_printf("\nError: Ran out of review slots!"); return -1; } GameDialogReviewEntry* entry = &(reviewList[curReviewSlot - 1]); entry->optionMessageListId = -4; entry->optionMessageId = -4; entry->optionText = (char*)mem_malloc(strlen(string) + 1); strcpy(entry->optionText, string); return 0; } // Creates dialog interface. // // 0x446288 static int gdProcessInit() { int upBtn; int downBtn; int optionsWindowX; int optionsWindowY; int fid; int replyWindowX = GAME_DIALOG_REPLY_WINDOW_X; int replyWindowY = GAME_DIALOG_REPLY_WINDOW_Y; gReplyWin = win_add(replyWindowX, replyWindowY, GAME_DIALOG_REPLY_WINDOW_WIDTH, GAME_DIALOG_REPLY_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x04); if (gReplyWin == -1) { goto err; } // Top part of the reply window - scroll up. upBtn = win_register_button(gReplyWin, 1, 1, 377, 28, -1, -1, KEY_ARROW_UP, -1, NULL, NULL, NULL, 32); if (upBtn == -1) { goto err_1; } win_register_button_sound_func(upBtn, gsound_red_butt_press, gsound_red_butt_release); win_register_button_func(upBtn, reply_arrow_up, reply_arrow_restore, 0, 0); // Bottom part of the reply window - scroll down. downBtn = win_register_button(gReplyWin, 1, 29, 377, 28, -1, -1, KEY_ARROW_DOWN, -1, NULL, NULL, NULL, 32); if (downBtn == -1) { goto err_1; } win_register_button_sound_func(downBtn, gsound_red_butt_press, gsound_red_butt_release); win_register_button_func(downBtn, reply_arrow_down, reply_arrow_restore, 0, 0); optionsWindowX = GAME_DIALOG_OPTIONS_WINDOW_X; optionsWindowY = GAME_DIALOG_OPTIONS_WINDOW_Y; gOptionWin = win_add(optionsWindowX, optionsWindowY, GAME_DIALOG_OPTIONS_WINDOW_WIDTH, GAME_DIALOG_OPTIONS_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x04); if (gOptionWin == -1) { goto err_2; } // di_rdbt2.frm - dialog red button down fid = art_id(OBJ_TYPE_INTERFACE, 96, 0, 0, 0); dialog_red_button_up_buf = art_ptr_lock_data(fid, 0, 0, &dialog_red_button_up_key); if (dialog_red_button_up_buf == NULL) { goto err_3; } // di_rdbt1.frm - dialog red button up fid = art_id(OBJ_TYPE_INTERFACE, 95, 0, 0, 0); dialog_red_button_down_buf = art_ptr_lock_data(fid, 0, 0, &dialog_red_button_down_key); if (dialog_red_button_down_buf == NULL) { goto err_3; } talkOldFont = text_curr(); text_font(101); return 0; err_3: art_ptr_unlock(dialog_red_button_up_key); dialog_red_button_up_key = NULL; err_2: win_delete(gOptionWin); gOptionWin = -1; err_1: win_delete(gReplyWin); gReplyWin = -1; err: return -1; } // RELASE: Rename/comment. // free dialog option buttons // 0x446454 static void gdProcessCleanup() { for (int index = 0; index < gdNumOptions; index++) { GameDialogOptionEntry* optionEntry = &(dialogBlock.options[index]); if (optionEntry->btn != -1) { win_delete_button(optionEntry->btn); optionEntry->btn = -1; } } } // RELASE: Rename/comment. // free dialog interface // 0x446498 static int gdProcessExit() { gdProcessCleanup(); art_ptr_unlock(dialog_red_button_down_key); dialog_red_button_down_key = NULL; dialog_red_button_down_buf = NULL; art_ptr_unlock(dialog_red_button_up_key); dialog_red_button_up_key = NULL; dialog_red_button_up_buf = NULL; win_delete(gReplyWin); gReplyWin = -1; win_delete(gOptionWin); gOptionWin = -1; text_font(talkOldFont); return 0; } // 0x446504 static void gdUpdateMula() { Rect rect; rect.ulx = 5; rect.lrx = 70; rect.uly = 36; rect.lry = text_height() + 36; talkToRefreshDialogWindowRect(&rect); int oldFont = text_curr(); text_font(101); int caps = item_caps_total(obj_dude); char text[20]; sprintf(text, "$%d", caps); int width = text_width(text); if (width > 60) { width = 60; } win_print(dialogueWindow, text, width, 38 - width / 2, 36, colorTable[992] | 0x7000000); text_font(oldFont); } // 0x4465C0 static int gdProcess() { if (gdReenterLevel == 0) { if (gdProcessInit() == -1) { return -1; } } gdReenterLevel += 1; gdProcessUpdate(); int v18 = 0; if (dialogBlock.offset != 0) { v18 = 1; gdReplyTooBig = 1; } unsigned int tick = get_time(); int pageCount = 0; int pageIndex = 0; int pageOffsets[10]; pageOffsets[0] = 0; for (;;) { int keyCode = get_input(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { break; } if (keyCode == KEY_CTRL_B && !mouse_click_in(135, 225, 514, 283)) { if (gmouse_get_cursor() != MOUSE_CURSOR_ARROW) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); } } else { if (dialogue_switch_mode == 3) { dialogue_state = 4; barter_inventory(dialogueWindow, dialog_target, peon_table_obj, barterer_table_obj, gdBarterMod); gdialog_barter_cleanup_tables(); int v5 = dialogue_state; gdialog_barter_destroy_win(); dialogue_state = v5; if (v5 == 4) { dialogue_switch_mode = 1; dialogue_state = 1; } continue; } else if (dialogue_switch_mode == 9) { dialogue_state = 10; gdControl(); gdControlDestroyWin(); continue; } else if (dialogue_switch_mode == 12) { dialogue_state = 13; gdCustom(); gdCustomDestroyWin(); continue; } if (keyCode == KEY_LOWERCASE_B) { gdialog_barter_pressed(-1, -1); } } if (gdReplyTooBig) { unsigned int v6 = get_bk_time(); if (v18) { if (elapsed_tocks(v6, tick) >= 10000 || keyCode == KEY_SPACE) { pageCount++; pageIndex++; pageOffsets[pageCount] = dialogBlock.offset; gdProcessReply(); tick = v6; if (!dialogBlock.offset) { v18 = 0; } } } if (keyCode == KEY_ARROW_UP) { if (pageIndex > 0) { pageIndex--; dialogBlock.offset = pageOffsets[pageIndex]; v18 = 0; gdProcessReply(); } } else if (keyCode == KEY_ARROW_DOWN) { if (pageIndex < pageCount) { pageIndex++; dialogBlock.offset = pageOffsets[pageIndex]; v18 = 0; gdProcessReply(); } else { if (dialogBlock.offset != 0) { tick = v6; pageIndex++; pageCount++; pageOffsets[pageCount] = dialogBlock.offset; v18 = 0; gdProcessReply(); } } } } if (keyCode != -1) { if (keyCode >= 1200 && keyCode <= 1250) { gdProcessHighlight(keyCode - 1200); } else if (keyCode >= 1300 && keyCode <= 1330) { gdProcessUnHighlight(keyCode - 1300); } else if (keyCode >= 48 && keyCode <= 57) { int v11 = keyCode - 49; if (v11 < gdNumOptions) { pageCount = 0; pageIndex = 0; pageOffsets[0] = 0; gdReplyTooBig = 0; if (gdProcessChoice(v11) == -1) { break; } tick = get_time(); if (dialogBlock.offset) { v18 = 1; gdReplyTooBig = 1; } else { v18 = 0; } } } } } gdReenterLevel -= 1; if (gdReenterLevel == 0) { if (gdProcessExit() == -1) { return -1; } } return 0; } // 0x4468DC static int gdProcessChoice(int a1) { // FIXME: There is a buffer underread bug when `a1` is -1 (pressing 0 on the // keyboard, see `gdProcess`). When it happens the game looks into unused // continuation of `dialogBlock.replyText` (within 0x58F868-0x58FF70 range) which // is initialized to 0 according to C spec. I was not able to replicate the // same behaviour by extending dialogBlock.replyText to 2700 bytes or introduce // new 1800 bytes buffer in between, at least not in debug builds. In order // to preserve original behaviour this dummy dialog option entry is used. // // TODO: Recheck behaviour after introducing |GameDialogBlock|. GameDialogOptionEntry dummy; memset(&dummy, 0, sizeof(dummy)); mouse_hide(); gdProcessCleanup(); GameDialogOptionEntry* dialogOptionEntry = a1 != -1 ? &(dialogBlock.options[a1]) : &dummy; if (dialogOptionEntry->messageListId == -4) { gdAddReviewOptionChosenStr(dialogOptionEntry->text); } else { gdAddReviewOptionChosen(dialogOptionEntry->messageListId, dialogOptionEntry->messageId); } can_start_new_fidget = false; gdialogFreeSpeech(); int v1 = GAME_DIALOG_REACTION_NEUTRAL; switch (dialogOptionEntry->reaction) { case GAME_DIALOG_REACTION_GOOD: v1 = -1; break; case GAME_DIALOG_REACTION_NEUTRAL: v1 = 0; break; case GAME_DIALOG_REACTION_BAD: v1 = 1; break; default: // See 0x446907 in ecx but this branch should be unreachable. Due to the // bug described above, this code is reachable. v1 = GAME_DIALOG_REACTION_NEUTRAL; debug_printf("\nError: dialog: Empathy Perk: invalid reaction!"); break; } demo_copy_title(gReplyWin); demo_copy_options(gOptionWin); win_draw(gReplyWin); win_draw(gOptionWin); gdProcessHighlight(a1); talk_to_critter_reacts(v1); gdNumOptions = 0; if (gdReenterLevel < 2) { if (dialogOptionEntry->proc != 0) { executeProcedure(dialogBlock.program, dialogOptionEntry->proc); } } mouse_show(); if (gdNumOptions == 0) { return -1; } gdProcessUpdate(); return 0; } // 0x446A18 static void gdProcessHighlight(int index) { // FIXME: See explanation in `gdProcessChoice`. GameDialogOptionEntry dummy; memset(&dummy, 0, sizeof(dummy)); GameDialogOptionEntry* dialogOptionEntry = index != -1 ? &(dialogBlock.options[index]) : &dummy; if (dialogOptionEntry->btn == 0) { return; } optionRect.ulx = 0; optionRect.uly = dialogOptionEntry->field_14; optionRect.lrx = 391; optionRect.lry = dialogOptionEntry->field_39C; gDialogRefreshOptionsRect(gOptionWin, &optionRect); optionRect.ulx = 5; optionRect.lrx = 388; int color = colorTable[32747] | 0x2000000; if (perkHasRank(obj_dude, PERK_EMPATHY)) { color = colorTable[32747] | 0x2000000; switch (dialogOptionEntry->reaction) { case GAME_DIALOG_REACTION_GOOD: color = colorTable[31775] | 0x2000000; break; case GAME_DIALOG_REACTION_NEUTRAL: break; case GAME_DIALOG_REACTION_BAD: color = colorTable[32074] | 0x2000000; break; default: debug_printf("\nError: dialog: Empathy Perk: invalid reaction!"); break; } } // NOTE: Uninline. text_to_rect_wrapped(win_get_buf(gOptionWin), &optionRect, dialogOptionEntry->text, NULL, text_height(), 393, color); optionRect.ulx = 0; optionRect.lrx = 391; optionRect.uly = dialogOptionEntry->field_14; win_draw_rect(gOptionWin, &optionRect); } // 0x446B5C static void gdProcessUnHighlight(int index) { GameDialogOptionEntry* dialogOptionEntry = &(dialogBlock.options[index]); optionRect.ulx = 0; optionRect.uly = dialogOptionEntry->field_14; optionRect.lrx = 391; optionRect.lry = dialogOptionEntry->field_39C; gDialogRefreshOptionsRect(gOptionWin, &optionRect); int color = colorTable[992] | 0x2000000; if (perk_level(obj_dude, PERK_EMPATHY) != 0) { color = colorTable[32747] | 0x2000000; switch (dialogOptionEntry->reaction) { case GAME_DIALOG_REACTION_GOOD: color = colorTable[31] | 0x2000000; break; case GAME_DIALOG_REACTION_NEUTRAL: color = colorTable[992] | 0x2000000; break; case GAME_DIALOG_REACTION_BAD: color = colorTable[31744] | 0x2000000; break; default: debug_printf("\nError: dialog: Empathy Perk: invalid reaction!"); break; } } optionRect.ulx = 5; optionRect.lrx = 388; // NOTE: Uninline. text_to_rect_wrapped(win_get_buf(gOptionWin), &optionRect, dialogOptionEntry->text, NULL, text_height(), 393, color); optionRect.lrx = 391; optionRect.uly = dialogOptionEntry->field_14; optionRect.ulx = 0; win_draw_rect(gOptionWin, &optionRect); } // 0x446C94 static void gdProcessReply() { replyRect.ulx = 5; replyRect.uly = 10; replyRect.lrx = 374; replyRect.lry = 58; // NOTE: There is an unused if condition. perk_level(obj_dude, PERK_EMPATHY); demo_copy_title(gReplyWin); // NOTE: Uninline. text_to_rect_wrapped(win_get_buf(gReplyWin), &replyRect, dialogBlock.replyText, &(dialogBlock.offset), text_height(), 379, colorTable[992] | 0x2000000); win_draw(gReplyWin); } // 0x446D30 static void gdProcessUpdate() { replyRect.ulx = 5; replyRect.uly = 10; replyRect.lrx = 374; replyRect.lry = 58; optionRect.ulx = 5; optionRect.uly = 5; optionRect.lrx = 388; optionRect.lry = 112; demo_copy_title(gReplyWin); demo_copy_options(gOptionWin); if (dialogBlock.replyMessageListId > 0) { char* s = scr_get_msg_str_speech(dialogBlock.replyMessageListId, dialogBlock.replyMessageId, 1); if (s == NULL) { GNWSystemError("\n'GDialog::Error Grabbing text message!"); exit(1); } strncpy(dialogBlock.replyText, s, sizeof(dialogBlock.replyText) - 1); *(dialogBlock.replyText + sizeof(dialogBlock.replyText) - 1) = '\0'; } gdProcessReply(); int color = colorTable[992] | 0x2000000; bool hasEmpathy = perk_level(obj_dude, PERK_EMPATHY) != 0; int width = optionRect.lrx - optionRect.ulx - 4; MessageListItem messageListItem; int v21 = 0; for (int index = 0; index < gdNumOptions; index++) { GameDialogOptionEntry* dialogOptionEntry = &(dialogBlock.options[index]); if (hasEmpathy) { switch (dialogOptionEntry->reaction) { case GAME_DIALOG_REACTION_GOOD: color = colorTable[31] | 0x2000000; break; case GAME_DIALOG_REACTION_NEUTRAL: color = colorTable[992] | 0x2000000; break; case GAME_DIALOG_REACTION_BAD: color = colorTable[31744] | 0x2000000; break; default: debug_printf("\nError: dialog: Empathy Perk: invalid reaction!"); break; } } if (dialogOptionEntry->messageListId >= 0) { char* text = scr_get_msg_str_speech(dialogOptionEntry->messageListId, dialogOptionEntry->messageId, 0); if (text == NULL) { GNWSystemError("\nGDialog::Error Grabbing text message!"); exit(1); } sprintf(dialogOptionEntry->text, "%c ", '\x95'); strncat(dialogOptionEntry->text, text, 897); } else if (dialogOptionEntry->messageListId == -1) { if (index == 0) { // Go on messageListItem.num = 655; if (critterGetStat(obj_dude, STAT_INTELLIGENCE) < 4) { if (message_search(&proto_main_msg_file, &messageListItem)) { strcpy(dialogOptionEntry->text, messageListItem.text); } else { debug_printf("\nError...can't find message!"); return; } } } else { // TODO: Why only space? strcpy(dialogOptionEntry->text, " "); } } else if (dialogOptionEntry->messageListId == -2) { // [Done] messageListItem.num = 650; if (message_search(&proto_main_msg_file, &messageListItem)) { sprintf(dialogOptionEntry->text, "%c %s", '\x95', messageListItem.text); } else { debug_printf("\nError...can't find message!"); return; } } int v11 = text_num_lines(dialogOptionEntry->text, optionRect.lrx - optionRect.ulx) * text_height() + optionRect.uly + 2; if (v11 < optionRect.lry) { int y = optionRect.uly; dialogOptionEntry->field_39C = v11; dialogOptionEntry->field_14 = y; if (index == 0) { y = 0; } // NOTE: Uninline. text_to_rect_wrapped(win_get_buf(gOptionWin), &optionRect, dialogOptionEntry->text, NULL, text_height(), 393, color); optionRect.uly += 2; dialogOptionEntry->btn = win_register_button(gOptionWin, 2, y, width, optionRect.uly - y - 4, 1200 + index, 1300 + index, -1, 49 + index, NULL, NULL, NULL, 0); if (dialogOptionEntry->btn != -1) { win_register_button_sound_func(dialogOptionEntry->btn, gsound_red_butt_press, gsound_red_butt_release); } else { debug_printf("\nError: Can't create button!"); } } else { if (!v21) { v21 = 1; } else { debug_printf("Error: couldn't make button because it went below the window.\n"); } } } gdUpdateMula(); win_draw(gReplyWin); win_draw(gOptionWin); } // 0x44715C static int gdCreateHeadWindow() { dialogue_state = 1; int windowWidth = GAME_DIALOG_WINDOW_WIDTH; // NOTE: Uninline. talk_to_create_background_window(); talk_to_refresh_background_window(); unsigned char* buf = win_get_buf(dialogueBackWindow); for (int index = 0; index < 8; index++) { soundContinueAll(); Rect* rect = &(backgrndRects[index]); int width = rect->lrx - rect->ulx; int height = rect->lry - rect->uly; backgrndBufs[index] = (unsigned char*)mem_malloc(width * height); if (backgrndBufs[index] == NULL) { return -1; } unsigned char* src = buf; src += windowWidth * rect->uly + rect->ulx; buf_to_buf(src, width, height, windowWidth, backgrndBufs[index], width); } gdialog_window_create(); headWindowBuffer = win_get_buf(dialogueBackWindow) + windowWidth * 14 + 126; if (headWindowBuffer == NULL) { gdDestroyHeadWindow(); return -1; } return 0; } // 0x447294 static void gdDestroyHeadWindow() { if (dialogueWindow != -1) { headWindowBuffer = NULL; } if (dialogue_state == 1) { gdialog_window_destroy(); } else if (dialogue_state == 4) { gdialog_barter_destroy_win(); } if (dialogueBackWindow != -1) { win_delete(dialogueBackWindow); dialogueBackWindow = -1; } for (int index = 0; index < 8; index++) { mem_free(backgrndBufs[index]); } } // 0x447300 static void gdSetupFidget(int headFrmId, int reaction) { // 0x518900 static int phone_anim = 0; fidgetFrameCounter = 0; if (headFrmId == -1) { fidgetFID = -1; fidgetFp = NULL; fidgetKey = INVALID_CACHE_ENTRY; fidgetAnim = -1; fidgetTocksPerFrame = 0; fidgetLastTime = 0; gdDisplayFrame(NULL, 0); lipsFID = 0; lipsKey = NULL; lipsFp = 0; return; } int anim = HEAD_ANIMATION_NEUTRAL_PHONEMES; switch (reaction) { case FIDGET_GOOD: anim = HEAD_ANIMATION_GOOD_PHONEMES; break; case FIDGET_BAD: anim = HEAD_ANIMATION_BAD_PHONEMES; break; } if (lipsFID != 0) { if (anim != phone_anim) { if (art_ptr_unlock(lipsKey) == -1) { debug_printf("failure unlocking lips frame!\n"); } lipsKey = NULL; lipsFp = NULL; lipsFID = 0; } } if (lipsFID == 0) { phone_anim = anim; lipsFID = art_id(OBJ_TYPE_HEAD, headFrmId, anim, 0, 0); lipsFp = art_ptr_lock(lipsFID, &lipsKey); if (lipsFp == NULL) { debug_printf("failure!\n"); char stats[200]; cache_stats(&art_cache, stats); debug_printf("%s", stats); } } int fid = art_id(OBJ_TYPE_HEAD, headFrmId, reaction, 0, 0); int fidgetCount = art_head_fidgets(fid); if (fidgetCount == -1) { debug_printf("\tError - No available fidgets for given frame id\n"); return; } int chance = roll_random(1, 100) + dialogue_seconds_since_last_input / 2; int fidget = fidgetCount; switch (fidgetCount) { case 1: fidget = 1; break; case 2: if (chance < 68) { fidget = 1; } else { fidget = 2; } break; case 3: dialogue_seconds_since_last_input = 0; if (chance < 52) { fidget = 1; } else if (chance < 77) { fidget = 2; } else { fidget = 3; } break; } debug_printf("Choosing fidget %d out of %d\n", fidget, fidgetCount); if (fidgetFp != NULL) { if (art_ptr_unlock(fidgetKey) == -1) { debug_printf("failure!\n"); } } fidgetFID = art_id(OBJ_TYPE_HEAD, headFrmId, reaction, fidget, 0); fidgetFrameCounter = 0; fidgetFp = art_ptr_lock(fidgetFID, &fidgetKey); if (fidgetFp == NULL) { debug_printf("failure!\n"); char stats[200]; cache_stats(&art_cache, stats); debug_printf("%s", stats); } fidgetLastTime = 0; fidgetAnim = reaction; fidgetTocksPerFrame = 1000 / art_frame_fps(fidgetFp); } // 0x447598 static void gdWaitForFidget() { if (fidgetFp == NULL) { return; } if (dialogueWindow == -1) { return; } debug_printf("Waiting for fidget to complete...\n"); while (art_frame_max_frame(fidgetFp) > fidgetFrameCounter) { if (elapsed_time(fidgetLastTime) >= fidgetTocksPerFrame) { gdDisplayFrame(fidgetFp, fidgetFrameCounter); fidgetLastTime = get_time(); fidgetFrameCounter++; } } fidgetFrameCounter = 0; } // 0x447614 static void gdPlayTransition(int anim) { if (fidgetFp == NULL) { return; } if (dialogueWindow == -1) { return; } mouse_hide(); debug_printf("Starting transition...\n"); gdWaitForFidget(); if (fidgetFp != NULL) { if (art_ptr_unlock(fidgetKey) == -1) { debug_printf("\tError unlocking fidget in transition func..."); } fidgetFp = NULL; } CacheEntry* headFrmHandle; int headFid = art_id(OBJ_TYPE_HEAD, dialogue_head, anim, 0, 0); Art* headFrm = art_ptr_lock(headFid, &headFrmHandle); if (headFrm == NULL) { debug_printf("\tError locking transition...\n"); } unsigned int delay = 1000 / art_frame_fps(headFrm); int frame = 0; unsigned int time = 0; while (frame < art_frame_max_frame(headFrm)) { if (elapsed_time(time) >= delay) { gdDisplayFrame(headFrm, frame); time = get_time(); frame++; } } if (art_ptr_unlock(headFrmHandle) == -1) { debug_printf("\tError unlocking transition...\n"); } debug_printf("Finished transition...\n"); mouse_show(); } // 0x447724 static void reply_arrow_up(int btn, int keyCode) { if (gdReplyTooBig) { gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_UP); } } // 0x447738 static void reply_arrow_down(int btn, int keyCode) { if (gdReplyTooBig) { gmouse_set_cursor(MOUSE_CURSOR_SMALL_ARROW_DOWN); } } // 0x44774C static void reply_arrow_restore(int btn, int keyCode) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); } // demo_copy_title // 0x447758 static void demo_copy_title(int win) { gd_replyWin = win; if (win == -1) { debug_printf("\nError: demo_copy_title: win invalid!"); return; } int width = win_width(win); if (width < 1) { debug_printf("\nError: demo_copy_title: width invalid!"); return; } int height = win_height(win); if (height < 1) { debug_printf("\nError: demo_copy_title: length invalid!"); return; } if (dialogueBackWindow == -1) { debug_printf("\nError: demo_copy_title: dialogueBackWindow wasn't created!"); return; } unsigned char* src = win_get_buf(dialogueBackWindow); if (src == NULL) { debug_printf("\nError: demo_copy_title: couldn't get buffer!"); return; } unsigned char* dest = win_get_buf(win); buf_to_buf(src + 640 * 225 + 135, width, height, 640, dest, width); } // demo_copy_options // 0x447818 static void demo_copy_options(int win) { gd_optionsWin = win; if (win == -1) { debug_printf("\nError: demo_copy_options: win invalid!"); return; } int width = win_width(win); if (width < 1) { debug_printf("\nError: demo_copy_options: width invalid!"); return; } int height = win_height(win); if (height < 1) { debug_printf("\nError: demo_copy_options: length invalid!"); return; } if (dialogueBackWindow == -1) { debug_printf("\nError: demo_copy_options: dialogueBackWindow wasn't created!"); return; } Rect windowRect; win_get_rect(dialogueWindow, &windowRect); unsigned char* src = win_get_buf(dialogueWindow); if (src == NULL) { debug_printf("\nError: demo_copy_options: couldn't get buffer!"); return; } unsigned char* dest = win_get_buf(win); buf_to_buf(src + 640 * (335 - windowRect.uly) + 127, width, height, 640, dest, width); } // gDialogRefreshOptionsRect // 0x447914 static void gDialogRefreshOptionsRect(int win, Rect* drawRect) { if (drawRect == NULL) { debug_printf("\nError: gDialogRefreshOptionsRect: drawRect NULL!"); return; } if (win == -1) { debug_printf("\nError: gDialogRefreshOptionsRect: win invalid!"); return; } if (dialogueBackWindow == -1) { debug_printf("\nError: gDialogRefreshOptionsRect: dialogueBackWindow wasn't created!"); return; } Rect windowRect; win_get_rect(dialogueWindow, &windowRect); unsigned char* src = win_get_buf(dialogueWindow); if (src == NULL) { debug_printf("\nError: gDialogRefreshOptionsRect: couldn't get buffer!"); return; } if (drawRect->uly >= drawRect->lry) { debug_printf("\nError: gDialogRefreshOptionsRect: Invalid Rect (too many options)!"); return; } if (drawRect->ulx >= drawRect->lrx) { debug_printf("\nError: gDialogRefreshOptionsRect: Invalid Rect (too many options)!"); return; } int destWidth = win_width(win); unsigned char* dest = win_get_buf(win); buf_to_buf( src + (640 * (335 - windowRect.uly) + 127) + (640 * drawRect->uly + drawRect->ulx), drawRect->lrx - drawRect->ulx, drawRect->lry - drawRect->uly, 640, dest + destWidth * drawRect->uly, destWidth); } // 0x447A58 static void gdialog_bk() { // 0x518904 static int loop_cnt = -1; // 0x518908 static unsigned int tocksWaiting = 10000; switch (dialogue_switch_mode) { case 2: loop_cnt = -1; dialogue_switch_mode = 3; gdialog_window_destroy(); gdialog_barter_create_win(); break; case 1: loop_cnt = -1; dialogue_switch_mode = 0; gdialog_barter_destroy_win(); gdialog_window_create(); // NOTE: Uninline. gdUnhide(); break; case 8: loop_cnt = -1; dialogue_switch_mode = 9; gdialog_window_destroy(); gdControlCreateWin(); break; case 11: loop_cnt = -1; dialogue_switch_mode = 12; gdialog_window_destroy(); gdCustomCreateWin(); break; } if (fidgetFp == NULL) { return; } if (gdialog_speech_playing) { lips_bkg_proc(); if (lips_draw_head) { gdDisplayFrame(lipsFp, head_phoneme_lookup[head_phoneme_current]); lips_draw_head = false; } if (!soundPlaying(lip_info.sound)) { gdialogFreeSpeech(); gdDisplayFrame(lipsFp, 0); can_start_new_fidget = true; dialogue_seconds_since_last_input = 3; fidgetFrameCounter = 0; } return; } if (can_start_new_fidget) { if (elapsed_time(fidgetLastTime) >= tocksWaiting) { can_start_new_fidget = false; dialogue_seconds_since_last_input += tocksWaiting / 1000; tocksWaiting = 1000 * (roll_random(0, 3) + 4); gdSetupFidget(fidgetFID & 0xFFF, (fidgetFID & 0xFF0000) >> 16); } return; } if (elapsed_time(fidgetLastTime) >= fidgetTocksPerFrame) { if (art_frame_max_frame(fidgetFp) <= fidgetFrameCounter) { gdDisplayFrame(fidgetFp, 0); can_start_new_fidget = true; } else { gdDisplayFrame(fidgetFp, fidgetFrameCounter); fidgetLastTime = get_time(); fidgetFrameCounter += 1; } } } // FIXME: Due to the bug in `gdProcessChoice` this function can receive invalid // reaction value (50 instead of expected -1, 0, 1). It's handled gracefully by // the game. // // 0x447CA0 void talk_to_critter_reacts(int a1) { int v1 = a1 + 1; debug_printf("Dialogue Reaction: "); if (v1 < 3) { debug_printf("%s\n", react_strs[v1]); } int v3 = a1 + 50; dialogue_seconds_since_last_input = 0; switch (v3) { case GAME_DIALOG_REACTION_GOOD: switch (fidgetAnim) { case FIDGET_GOOD: gdPlayTransition(HEAD_ANIMATION_VERY_GOOD_REACTION); gdSetupFidget(dialogue_head, FIDGET_GOOD); break; case FIDGET_NEUTRAL: gdPlayTransition(HEAD_ANIMATION_NEUTRAL_TO_GOOD); gdSetupFidget(dialogue_head, FIDGET_GOOD); break; case FIDGET_BAD: gdPlayTransition(HEAD_ANIMATION_BAD_TO_NEUTRAL); gdSetupFidget(dialogue_head, FIDGET_NEUTRAL); break; } break; case GAME_DIALOG_REACTION_NEUTRAL: break; case GAME_DIALOG_REACTION_BAD: switch (fidgetAnim) { case FIDGET_GOOD: gdPlayTransition(HEAD_ANIMATION_GOOD_TO_NEUTRAL); gdSetupFidget(dialogue_head, FIDGET_NEUTRAL); break; case FIDGET_NEUTRAL: gdPlayTransition(HEAD_ANIMATION_NEUTRAL_TO_BAD); gdSetupFidget(dialogue_head, FIDGET_BAD); break; case FIDGET_BAD: gdPlayTransition(HEAD_ANIMATION_VERY_BAD_REACTION); gdSetupFidget(dialogue_head, FIDGET_BAD); break; } break; } } // 0x447D98 static void gdialog_scroll_subwin(int win, int a2, unsigned char* a3, unsigned char* a4, unsigned char* a5, int a6, int a7) { int v7; unsigned char* v9; Rect rect; unsigned int tick; v7 = a6; v9 = a4; if (a2 == 1) { rect.ulx = 0; rect.lrx = GAME_DIALOG_WINDOW_WIDTH - 1; rect.lry = a6 - 1; int v18 = a6 / 10; if (a7 == -1) { rect.uly = 10; v18 = 0; } else { rect.uly = v18 * 10; v7 = a6 % 10; v9 += (GAME_DIALOG_WINDOW_WIDTH) * rect.uly; } for (; v18 >= 0; v18--) { soundContinueAll(); buf_to_buf(a3, GAME_DIALOG_WINDOW_WIDTH, v7, GAME_DIALOG_WINDOW_WIDTH, v9, GAME_DIALOG_WINDOW_WIDTH); rect.uly -= 10; win_draw_rect(win, &rect); v7 += 10; v9 -= 10 * (GAME_DIALOG_WINDOW_WIDTH); tick = get_time(); while (elapsed_time(tick) < 33) { } } } else { rect.lrx = GAME_DIALOG_WINDOW_WIDTH - 1; rect.lry = a6 - 1; rect.ulx = 0; rect.uly = 0; for (int index = a6 / 10; index > 0; index--) { soundContinueAll(); buf_to_buf(a5, GAME_DIALOG_WINDOW_WIDTH, 10, GAME_DIALOG_WINDOW_WIDTH, v9, GAME_DIALOG_WINDOW_WIDTH); v9 += 10 * (GAME_DIALOG_WINDOW_WIDTH); v7 -= 10; a5 += 10 * (GAME_DIALOG_WINDOW_WIDTH); buf_to_buf(a3, GAME_DIALOG_WINDOW_WIDTH, v7, GAME_DIALOG_WINDOW_WIDTH, v9, GAME_DIALOG_WINDOW_WIDTH); win_draw_rect(win, &rect); rect.uly += 10; tick = get_time(); while (elapsed_time(tick) < 33) { } } } } // 0x447F64 static int text_num_lines(const char* a1, int a2) { int width = text_width(a1); int v1 = 0; while (width > 0) { width -= a2; v1++; } return v1; } // NOTE: Inlined. // // 0x447F80 static int text_to_rect_wrapped(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color) { return text_to_rect_func(buffer, rect, string, a4, height, pitch, color, 1); } // display_msg // 0x447FA0 static int text_to_rect_func(unsigned char* buffer, Rect* rect, char* string, int* a4, int height, int pitch, int color, int a7) { char* start; if (a4 != NULL) { start = string + *a4; } else { start = string; } int maxWidth = rect->lrx - rect->ulx; char* end = NULL; while (start != NULL && *start != '\0') { if (text_width(start) > maxWidth) { end = start + 1; while (*end != '\0' && *end != ' ') { end++; } if (*end != '\0') { char* lookahead = end + 1; while (lookahead != NULL) { while (*lookahead != '\0' && *lookahead != ' ') { lookahead++; } if (*lookahead == '\0') { lookahead = NULL; } else { *lookahead = '\0'; if (text_width(start) >= maxWidth) { *lookahead = ' '; lookahead = NULL; } else { end = lookahead; *lookahead = ' '; lookahead++; } } } if (*end == ' ') { *end = '\0'; } } else { if (rect->lry - text_height() < rect->uly) { return rect->uly; } if (a7 != 1 || start == string) { text_to_buf(buffer + pitch * rect->uly + 10, start, maxWidth, pitch, color); } else { text_to_buf(buffer + pitch * rect->uly, start, maxWidth, pitch, color); } if (a4 != NULL) { *a4 += strlen(start) + 1; } rect->uly += height; return rect->uly; } } if (text_width(start) > maxWidth) { debug_printf("\nError: display_msg: word too long!"); break; } if (a7 != 0) { if (rect->lry - text_height() < rect->uly) { if (end != NULL && *end == '\0') { *end = ' '; } return rect->uly; } unsigned char* dest; if (a7 != 1 || start == string) { dest = buffer + 10; } else { dest = buffer; } text_to_buf(dest + pitch * rect->uly, start, maxWidth, pitch, color); } if (a4 != NULL && end != NULL) { *a4 += strlen(start) + 1; } rect->uly += height; if (end != NULL) { start = end + 1; if (*end == '\0') { *end = ' '; } end = NULL; } else { start = NULL; } } if (a4 != NULL) { *a4 = 0; } return rect->uly; } // 0x448214 void gdialogSetBarterMod(int modifier) { gdBarterMod = modifier; } // gdialog_barter // 0x44821C int gdActivateBarter(int modifier) { if (!dialog_state_fix) { return -1; } gdBarterMod = modifier; gdialog_barter_pressed(-1, -1); dialogue_state = 4; dialogue_switch_mode = 2; return 0; } // 0x448268 void barter_end_to_talk_to() { dialogQuit(); dialogClose(); updatePrograms(); updateWindows(); dialogue_state = 1; dialogue_switch_mode = 1; } // 0x448290 static int gdialog_barter_create_win() { dialogue_state = 4; int frmId; if (dialog_target_is_party) { // trade.frm - party member barter/trade interface frmId = 420; } else { // barter.frm - barter window frmId = 111; } int backgroundFid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0); CacheEntry* backgroundHandle; Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundHandle); if (backgroundFrm == NULL) { return -1; } unsigned char* backgroundData = art_frame_data(backgroundFrm, 0, 0); if (backgroundData == NULL) { art_ptr_unlock(backgroundHandle); return -1; } dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0); int barterWindowX = 0; int barterWindowY = GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len; dialogueWindow = win_add(barterWindowX, barterWindowY, GAME_DIALOG_WINDOW_WIDTH, dialogue_subwin_len, 256, WINDOW_FLAG_0x02); if (dialogueWindow == -1) { art_ptr_unlock(backgroundHandle); return -1; } int width = GAME_DIALOG_WINDOW_WIDTH; unsigned char* windowBuffer = win_get_buf(dialogueWindow); unsigned char* backgroundWindowBuffer = win_get_buf(dialogueBackWindow); buf_to_buf(backgroundWindowBuffer + width * (480 - dialogue_subwin_len), width, dialogue_subwin_len, width, windowBuffer, width); gdialog_scroll_subwin(dialogueWindow, 1, backgroundData, windowBuffer, NULL, dialogue_subwin_len, 0); art_ptr_unlock(backgroundHandle); // TRADE gdialog_buttons[0] = win_register_button(dialogueWindow, 41, 163, 14, 14, -1, -1, -1, KEY_LOWERCASE_M, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[0] != -1) { win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release); // TALK gdialog_buttons[1] = win_register_button(dialogueWindow, 584, 162, 14, 14, -1, -1, -1, KEY_LOWERCASE_T, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[1] != -1) { win_register_button_sound_func(gdialog_buttons[1], gsound_med_butt_press, gsound_med_butt_release); if (obj_new(&peon_table_obj, -1, -1) != -1) { peon_table_obj->flags |= OBJECT_HIDDEN; if (obj_new(&barterer_table_obj, -1, -1) != -1) { barterer_table_obj->flags |= OBJECT_HIDDEN; if (obj_new(&barterer_temp_obj, dialog_target->fid, -1) != -1) { barterer_temp_obj->flags |= OBJECT_HIDDEN | OBJECT_TEMPORARY; barterer_temp_obj->sid = -1; return 0; } obj_erase_object(barterer_table_obj, 0); } obj_erase_object(peon_table_obj, 0); } win_delete_button(gdialog_buttons[1]); gdialog_buttons[1] = -1; } win_delete_button(gdialog_buttons[0]); gdialog_buttons[0] = -1; } win_delete(dialogueWindow); dialogueWindow = -1; return -1; } // 0x44854C static void gdialog_barter_destroy_win() { if (dialogueWindow == -1) { return; } obj_erase_object(barterer_temp_obj, 0); obj_erase_object(barterer_table_obj, 0); obj_erase_object(peon_table_obj, 0); for (int index = 0; index < 9; index++) { win_delete_button(gdialog_buttons[index]); gdialog_buttons[index] = -1; } unsigned char* backgroundWindowBuffer = win_get_buf(dialogueBackWindow); backgroundWindowBuffer += (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len); int frmId; if (dialog_target_is_party) { // trade.frm - party member barter/trade interface frmId = 420; } else { // barter.frm - barter window frmId = 111; } CacheEntry* backgroundFrmHandle; int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { unsigned char* windowBuffer = win_get_buf(dialogueWindow); gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, windowBuffer, backgroundWindowBuffer, dialogue_subwin_len, 0); art_ptr_unlock(backgroundFrmHandle); } win_delete(dialogueWindow); dialogueWindow = -1; cai_attempt_w_reload(dialog_target, 0); } // 0x448660 static void gdialog_barter_cleanup_tables() { Inventory* inventory; int length; inventory = &(peon_table_obj->data.inventory); length = inventory->length; for (int index = 0; index < length; index++) { Object* item = inventory->items->item; int quantity = item_count(peon_table_obj, item); item_move_force(peon_table_obj, obj_dude, item, quantity); } inventory = &(barterer_table_obj->data.inventory); length = inventory->length; for (int index = 0; index < length; index++) { Object* item = inventory->items->item; int quantity = item_count(barterer_table_obj, item); item_move_force(barterer_table_obj, dialog_target, item, quantity); } if (barterer_temp_obj != NULL) { inventory = &(barterer_temp_obj->data.inventory); length = inventory->length; for (int index = 0; index < length; index++) { Object* item = inventory->items->item; int quantity = item_count(barterer_temp_obj, item); item_move_force(barterer_temp_obj, dialog_target, item, quantity); } } } // 0x448740 static int gdControlCreateWin() { CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 390, 0, 0, 0); Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle); if (backgroundFrm == NULL) { return -1; } unsigned char* backgroundData = art_frame_data(backgroundFrm, 0, 0); if (backgroundData == NULL) { gdControlDestroyWin(); return -1; } dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0); int controlWindowX = 0; int controlWindowY = GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len; dialogueWindow = win_add(controlWindowX, controlWindowY, GAME_DIALOG_WINDOW_WIDTH, dialogue_subwin_len, 256, WINDOW_FLAG_0x02); if (dialogueWindow == -1) { gdControlDestroyWin(); return -1; } unsigned char* windowBuffer = win_get_buf(dialogueWindow); unsigned char* src = win_get_buf(dialogueBackWindow); buf_to_buf(src + (GAME_DIALOG_WINDOW_WIDTH) * (GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len), GAME_DIALOG_WINDOW_WIDTH, dialogue_subwin_len, GAME_DIALOG_WINDOW_WIDTH, windowBuffer, GAME_DIALOG_WINDOW_WIDTH); gdialog_scroll_subwin(dialogueWindow, 1, backgroundData, windowBuffer, 0, dialogue_subwin_len, 0); art_ptr_unlock(backgroundFrmHandle); // TALK gdialog_buttons[0] = win_register_button(dialogueWindow, 593, 41, 14, 14, -1, -1, -1, KEY_ESCAPE, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[0] == -1) { gdControlDestroyWin(); return -1; } win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release); // TRADE gdialog_buttons[1] = win_register_button(dialogueWindow, 593, 97, 14, 14, -1, -1, -1, KEY_LOWERCASE_D, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[1] == -1) { gdControlDestroyWin(); return -1; } win_register_button_sound_func(gdialog_buttons[1], gsound_med_butt_press, gsound_med_butt_release); // USE BEST WEAPON gdialog_buttons[2] = win_register_button(dialogueWindow, 236, 15, 14, 14, -1, -1, -1, KEY_LOWERCASE_W, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[2] == -1) { gdControlDestroyWin(); return -1; } win_register_button_sound_func(gdialog_buttons[1], gsound_med_butt_press, gsound_med_butt_release); // USE BEST ARMOR gdialog_buttons[3] = win_register_button(dialogueWindow, 235, 46, 14, 14, -1, -1, -1, KEY_LOWERCASE_A, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[3] == -1) { gdControlDestroyWin(); return -1; } win_register_button_sound_func(gdialog_buttons[2], gsound_med_butt_press, gsound_med_butt_release); control_buttons_start = 4; int v21 = 3; for (int index = 0; index < 5; index++) { GameDialogButtonData* buttonData = &(control_button_info[index]); int fid; fid = art_id(OBJ_TYPE_INTERFACE, buttonData->upFrmId, 0, 0, 0); Art* upButtonFrm = art_ptr_lock(fid, &(buttonData->upFrmHandle)); if (upButtonFrm == NULL) { gdControlDestroyWin(); return -1; } int width = art_frame_width(upButtonFrm, 0, 0); int height = art_frame_length(upButtonFrm, 0, 0); unsigned char* upButtonFrmData = art_frame_data(upButtonFrm, 0, 0); fid = art_id(OBJ_TYPE_INTERFACE, buttonData->downFrmId, 0, 0, 0); Art* downButtonFrm = art_ptr_lock(fid, &(buttonData->downFrmHandle)); if (downButtonFrm == NULL) { gdControlDestroyWin(); return -1; } unsigned char* downButtonFrmData = art_frame_data(downButtonFrm, 0, 0); fid = art_id(OBJ_TYPE_INTERFACE, buttonData->disabledFrmId, 0, 0, 0); Art* disabledButtonFrm = art_ptr_lock(fid, &(buttonData->disabledFrmHandle)); if (disabledButtonFrm == NULL) { gdControlDestroyWin(); return -1; } unsigned char* disabledButtonFrmData = art_frame_data(disabledButtonFrm, 0, 0); v21++; gdialog_buttons[v21] = win_register_button(dialogueWindow, buttonData->x, buttonData->y, width, height, -1, -1, buttonData->keyCode, -1, upButtonFrmData, downButtonFrmData, NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x04 | BUTTON_FLAG_0x01); if (gdialog_buttons[v21] == -1) { gdControlDestroyWin(); return -1; } win_register_button_disable(gdialog_buttons[v21], disabledButtonFrmData, disabledButtonFrmData, disabledButtonFrmData); win_register_button_sound_func(gdialog_buttons[v21], gsound_med_butt_press, gsound_med_butt_release); if (!partyMemberHasAIDisposition(dialog_target, buttonData->value)) { win_disable_button(gdialog_buttons[v21]); } } win_group_radio_buttons(5, &(gdialog_buttons[control_buttons_start])); int disposition = ai_get_disposition(dialog_target); win_set_button_rest_state(gdialog_buttons[control_buttons_start + 4 - disposition], 1, 0); gdControlUpdateInfo(); dialogue_state = 10; win_draw(dialogueWindow); return 0; } // 0x448C10 static void gdControlDestroyWin() { if (dialogueWindow == -1) { return; } for (int index = 0; index < 9; index++) { win_delete_button(gdialog_buttons[index]); gdialog_buttons[index] = -1; } for (int index = 0; index < 5; index++) { GameDialogButtonData* buttonData = &(control_button_info[index]); if (buttonData->upFrmHandle) { art_ptr_unlock(buttonData->upFrmHandle); buttonData->upFrmHandle = NULL; } if (buttonData->downFrmHandle) { art_ptr_unlock(buttonData->downFrmHandle); buttonData->downFrmHandle = NULL; } if (buttonData->disabledFrmHandle) { art_ptr_unlock(buttonData->disabledFrmHandle); buttonData->disabledFrmHandle = NULL; } } // control.frm - party member control interface CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 390, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, win_get_buf(dialogueWindow), win_get_buf(dialogueBackWindow) + (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len), dialogue_subwin_len, 0); art_ptr_unlock(backgroundFrmHandle); } win_delete(dialogueWindow); dialogueWindow = -1; } // 0x448D30 static void gdControlUpdateInfo() { int oldFont = text_curr(); text_font(101); unsigned char* windowBuffer = win_get_buf(dialogueWindow); int windowWidth = win_width(dialogueWindow); CacheEntry* backgroundHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 390, 0, 0, 0); Art* background = art_ptr_lock(backgroundFid, &backgroundHandle); if (background != NULL) { int width = art_frame_width(background, 0, 0); unsigned char* buffer = art_frame_data(background, 0, 0); // Clear "Weapon Used:". buf_to_buf(buffer + width * 20 + 112, 110, text_height(), width, windowBuffer + windowWidth * 20 + 112, windowWidth); // Clear "Armor Used:". buf_to_buf(buffer + width * 49 + 112, 110, text_height(), width, windowBuffer + windowWidth * 49 + 112, windowWidth); // Clear character preview. buf_to_buf(buffer + width * 84 + 8, 70, 98, width, windowBuffer + windowWidth * 84 + 8, windowWidth); // Clear ? buf_to_buf(buffer + width * 80 + 232, 132, 106, width, windowBuffer + windowWidth * 80 + 232, windowWidth); art_ptr_unlock(backgroundHandle); } MessageListItem messageListItem; char* text; char formattedText[256]; // Render item in right hand. Object* item2 = inven_right_hand(dialog_target); text = item2 != NULL ? item_name(item2) : getmsg(&proto_main_msg_file, &messageListItem, 10); sprintf(formattedText, "%s", text); text_to_buf(windowBuffer + windowWidth * 20 + 112, formattedText, 110, windowWidth, colorTable[992]); // Render armor. Object* armor = inven_worn(dialog_target); text = armor != NULL ? item_name(armor) : getmsg(&proto_main_msg_file, &messageListItem, 10); sprintf(formattedText, "%s", text); text_to_buf(windowBuffer + windowWidth * 49 + 112, formattedText, 110, windowWidth, colorTable[992]); // Render preview. CacheEntry* previewHandle; int previewFid = art_id(FID_TYPE(dialog_target->fid), dialog_target->fid & 0xFFF, ANIM_STAND, (dialog_target->fid & 0xF000) >> 12, ROTATION_SW); Art* preview = art_ptr_lock(previewFid, &previewHandle); if (preview != NULL) { int width = art_frame_width(preview, 0, ROTATION_SW); int height = art_frame_length(preview, 0, ROTATION_SW); unsigned char* buffer = art_frame_data(preview, 0, ROTATION_SW); trans_buf_to_buf(buffer, width, height, width, windowBuffer + windowWidth * (132 - height / 2) + 39 - width / 2, windowWidth); art_ptr_unlock(previewHandle); } // Render hit points. int maximumHitPoints = critterGetStat(dialog_target, STAT_MAXIMUM_HIT_POINTS); int hitPoints = critterGetStat(dialog_target, STAT_CURRENT_HIT_POINTS); sprintf(formattedText, "%d/%d", hitPoints, maximumHitPoints); text_to_buf(windowBuffer + windowWidth * 96 + 240, formattedText, 115, windowWidth, colorTable[992]); // Render best skill. int bestSkill = partyMemberSkill(dialog_target); text = skill_name(bestSkill); sprintf(formattedText, "%s", text); text_to_buf(windowBuffer + windowWidth * 113 + 240, formattedText, 115, windowWidth, colorTable[992]); // Render weight summary. int inventoryWeight = item_total_weight(dialog_target); int carryWeight = critterGetStat(dialog_target, STAT_CARRY_WEIGHT); sprintf(formattedText, "%d/%d ", inventoryWeight, carryWeight); text_to_buf(windowBuffer + windowWidth * 131 + 240, formattedText, 115, windowWidth, critterIsOverloaded(dialog_target) ? colorTable[31744] : colorTable[992]); // Render melee damage. int meleeDamage = critterGetStat(dialog_target, STAT_MELEE_DAMAGE); sprintf(formattedText, "%d", meleeDamage); text_to_buf(windowBuffer + windowWidth * 148 + 240, formattedText, 115, windowWidth, colorTable[992]); int actionPoints; if (isInCombat()) { actionPoints = dialog_target->data.critter.combat.ap; } else { actionPoints = critterGetStat(dialog_target, STAT_MAXIMUM_ACTION_POINTS); } int maximumActionPoints = critterGetStat(dialog_target, STAT_MAXIMUM_ACTION_POINTS); sprintf(formattedText, "%d/%d ", actionPoints, maximumActionPoints); text_to_buf(windowBuffer + windowWidth * 167 + 240, formattedText, 115, windowWidth, colorTable[992]); text_font(oldFont); win_draw(dialogueWindow); } // 0x44928C static void gdControlPressed(int btn, int keyCode) { dialogue_switch_mode = 8; dialogue_state = 10; // NOTE: Uninline. gdHide(); } // 0x4492D0 static int gdPickAIUpdateMsg(Object* critter) { // TODO: Check. // 0x444D10 static const int pids[3] = { 0x1000088, 0x1000156, 0x1000180, }; for (int index = 0; index < 3; index++) { if (critter->pid == pids[index]) { return 677 + roll_random(0, 1); } } return 670 + roll_random(0, 4); } // 0x449330 static int gdCanBarter() { if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_CRITTER) { return 1; } Proto* proto; if (proto_ptr(dialog_target->pid, &proto) == -1) { return 1; } if (proto->critter.data.flags & CRITTER_BARTER) { return 1; } MessageListItem messageListItem; // This person will not barter with you. messageListItem.num = 903; if (dialog_target_is_party) { // This critter can't carry anything. messageListItem.num = 913; } if (!message_search(&proto_main_msg_file, &messageListItem)) { debug_printf("\nError: gdialog: Can't find message!"); return 0; } gdialogDisplayMsg(messageListItem.text); return 0; } // 0x4493B8 static void gdControl() { MessageListItem messageListItem; bool done = false; while (!done) { int keyCode = get_input(); if (keyCode != -1) { if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { break; } if (keyCode == KEY_LOWERCASE_W) { inven_unwield(dialog_target, 1); Object* weapon = ai_search_inven_weap(dialog_target, 0, NULL); if (weapon != NULL) { inven_wield(dialog_target, weapon, 1); cai_attempt_w_reload(dialog_target, 0); int num = gdPickAIUpdateMsg(dialog_target); char* msg = getmsg(&proto_main_msg_file, &messageListItem, num); gdialogDisplayMsg(msg); gdControlUpdateInfo(); } } else if (keyCode == 2098) { ai_set_disposition(dialog_target, 4); } else if (keyCode == 2099) { ai_set_disposition(dialog_target, 0); dialogue_state = 13; dialogue_switch_mode = 11; done = true; } else if (keyCode == 2102) { ai_set_disposition(dialog_target, 2); } else if (keyCode == 2103) { ai_set_disposition(dialog_target, 3); } else if (keyCode == 2111) { ai_set_disposition(dialog_target, 1); } else if (keyCode == KEY_ESCAPE) { dialogue_switch_mode = 1; dialogue_state = 1; return; } else if (keyCode == KEY_LOWERCASE_A) { if (dialog_target->pid != 0x10000A1) { Object* armor = ai_search_inven_armor(dialog_target); if (armor != NULL) { inven_wield(dialog_target, armor, 0); } } int num = gdPickAIUpdateMsg(dialog_target); char* msg = getmsg(&proto_main_msg_file, &messageListItem, num); gdialogDisplayMsg(msg); gdControlUpdateInfo(); } else if (keyCode == KEY_LOWERCASE_D) { if (gdCanBarter()) { dialogue_switch_mode = 2; dialogue_state = 4; return; } } else if (keyCode == -2) { if (mouse_click_in(441, 451, 540, 470)) { ai_set_disposition(dialog_target, 0); dialogue_state = 13; dialogue_switch_mode = 11; done = true; } } } } } // 0x4496A0 static int gdCustomCreateWin() { if (!message_init(&custom_msg_file)) { return -1; } if (!message_load(&custom_msg_file, "game\\custom.msg")) { return -1; } CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 391, 0, 0, 0); Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle); if (backgroundFrm == NULL) { return -1; } unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0); if (backgroundFrmData == NULL) { // FIXME: Leaking background. gdCustomDestroyWin(); return -1; } dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0); int customizationWindowX = 0; int customizationWindowY = GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len; dialogueWindow = win_add(customizationWindowX, customizationWindowY, GAME_DIALOG_WINDOW_WIDTH, dialogue_subwin_len, 256, WINDOW_FLAG_0x02); if (dialogueWindow == -1) { gdCustomDestroyWin(); return -1; } unsigned char* windowBuffer = win_get_buf(dialogueWindow); unsigned char* parentWindowBuffer = win_get_buf(dialogueBackWindow); buf_to_buf(parentWindowBuffer + (GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len) * GAME_DIALOG_WINDOW_WIDTH, GAME_DIALOG_WINDOW_WIDTH, dialogue_subwin_len, GAME_DIALOG_WINDOW_WIDTH, windowBuffer, GAME_DIALOG_WINDOW_WIDTH); gdialog_scroll_subwin(dialogueWindow, 1, backgroundFrmData, windowBuffer, NULL, dialogue_subwin_len, 0); art_ptr_unlock(backgroundFrmHandle); gdialog_buttons[0] = win_register_button(dialogueWindow, 593, 101, 14, 14, -1, -1, -1, 13, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[0] == -1) { gdCustomDestroyWin(); return -1; } win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release); int optionButton = 0; custom_buttons_start = 1; for (int index = 0; index < PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT; index++) { GameDialogButtonData* buttonData = &(custom_button_info[index]); int upButtonFid = art_id(OBJ_TYPE_INTERFACE, buttonData->upFrmId, 0, 0, 0); Art* upButtonFrm = art_ptr_lock(upButtonFid, &(buttonData->upFrmHandle)); if (upButtonFrm == NULL) { gdCustomDestroyWin(); return -1; } int width = art_frame_width(upButtonFrm, 0, 0); int height = art_frame_length(upButtonFrm, 0, 0); unsigned char* upButtonFrmData = art_frame_data(upButtonFrm, 0, 0); int downButtonFid = art_id(OBJ_TYPE_INTERFACE, buttonData->downFrmId, 0, 0, 0); Art* downButtonFrm = art_ptr_lock(downButtonFid, &(buttonData->downFrmHandle)); if (downButtonFrm == NULL) { gdCustomDestroyWin(); return -1; } unsigned char* downButtonFrmData = art_frame_data(downButtonFrm, 0, 0); optionButton++; gdialog_buttons[optionButton] = win_register_button(dialogueWindow, buttonData->x, buttonData->y, width, height, -1, -1, -1, buttonData->keyCode, upButtonFrmData, downButtonFrmData, NULL, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[optionButton] == -1) { gdCustomDestroyWin(); return -1; } win_register_button_sound_func(gdialog_buttons[index], gsound_med_butt_press, gsound_med_butt_release); } custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE] = ai_get_burst_value(dialog_target); custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE] = ai_get_run_away_value(dialog_target); custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON] = ai_get_weapon_pref_value(dialog_target); custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE] = ai_get_distance_pref_value(dialog_target); custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO] = ai_get_attack_who_value(dialog_target); custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE] = ai_get_chem_use_value(dialog_target); dialogue_state = 13; gdCustomUpdateInfo(); return 0; } // 0x449A10 static void gdCustomDestroyWin() { if (dialogueWindow == -1) { return; } for (int index = 0; index < 9; index++) { win_delete_button(gdialog_buttons[index]); gdialog_buttons[index] = -1; } for (int index = 0; index < PARTY_MEMBER_CUSTOMIZATION_OPTION_COUNT; index++) { GameDialogButtonData* buttonData = &(custom_button_info[index]); if (buttonData->upFrmHandle != NULL) { art_ptr_unlock(buttonData->upFrmHandle); buttonData->upFrmHandle = NULL; } if (buttonData->downFrmHandle != NULL) { art_ptr_unlock(buttonData->downFrmHandle); buttonData->downFrmHandle = NULL; } if (buttonData->disabledFrmHandle != NULL) { art_ptr_unlock(buttonData->disabledFrmHandle); buttonData->disabledFrmHandle = NULL; } } CacheEntry* backgroundFrmHandle; // custom.frm - party member control interface int fid = art_id(OBJ_TYPE_INTERFACE, 391, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, win_get_buf(dialogueWindow), win_get_buf(dialogueBackWindow) + (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len), dialogue_subwin_len, 0); art_ptr_unlock(backgroundFrmHandle); } win_delete(dialogueWindow); dialogueWindow = -1; message_exit(&custom_msg_file); } // 0x449B3C static void gdCustom() { bool done = false; while (!done) { unsigned int keyCode = get_input(); if (keyCode != -1) { if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { break; } if (keyCode <= 5) { gdCustomSelect(keyCode); gdCustomUpdateInfo(); } else if (keyCode == KEY_RETURN || keyCode == KEY_ESCAPE) { done = true; dialogue_switch_mode = 8; dialogue_state = 10; } } } } // 0x449BB4 static void gdCustomUpdateInfo() { int oldFont = text_curr(); text_font(101); unsigned char* windowBuffer = win_get_buf(dialogueWindow); int windowWidth = win_width(dialogueWindow); CacheEntry* backgroundHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 391, 0, 0, 0); Art* background = art_ptr_lock(backgroundFid, &backgroundHandle); if (background == NULL) { return; } int backgroundWidth = art_frame_width(background, 0, 0); int backgroundHeight = art_frame_length(background, 0, 0); unsigned char* backgroundData = art_frame_data(background, 0, 0); buf_to_buf(backgroundData, backgroundWidth, backgroundHeight, backgroundWidth, windowBuffer, GAME_DIALOG_WINDOW_WIDTH); art_ptr_unlock(backgroundHandle); MessageListItem messageListItem; int num; char* msg; // BURST if (custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE] == -1) { // Not Applicable num = 99; } else { debug_printf("\nburst: %d", custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE]); num = custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE]].messageId; } msg = getmsg(&custom_msg_file, &messageListItem, num); text_to_buf(windowBuffer + windowWidth * 20 + 232, msg, 248, windowWidth, colorTable[992]); // RUN AWAY msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE]].messageId); text_to_buf(windowBuffer + windowWidth * 48 + 232, msg, 248, windowWidth, colorTable[992]); // WEAPON PREF msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON]].messageId); text_to_buf(windowBuffer + windowWidth * 78 + 232, msg, 248, windowWidth, colorTable[992]); // DISTANCE msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE]].messageId); text_to_buf(windowBuffer + windowWidth * 108 + 232, msg, 248, windowWidth, colorTable[992]); // ATTACK WHO msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO]].messageId); text_to_buf(windowBuffer + windowWidth * 137 + 232, msg, 248, windowWidth, colorTable[992]); // CHEM USE msg = getmsg(&custom_msg_file, &messageListItem, custom_settings[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE][custom_current_selected[PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE]].messageId); text_to_buf(windowBuffer + windowWidth * 166 + 232, msg, 248, windowWidth, colorTable[992]); win_draw(dialogueWindow); text_font(oldFont); } // 0x449E64 static void gdCustomSelectRedraw(unsigned char* dest, int pitch, int type, int selectedIndex) { MessageListItem messageListItem; text_font(101); for (int index = 0; index < 6; index++) { STRUCT_5189E4* ptr = &(custom_settings[type][index]); if (ptr->messageId != -1) { bool enabled = false; switch (type) { case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE: enabled = partyMemberHasAIBurstValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE: enabled = partyMemberHasAIRunAwayValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON: enabled = partyMemberHasAIWeaponPrefValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE: enabled = partyMemberHasAIDistancePrefValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO: enabled = partyMemberHasAIAttackWhoValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE: enabled = partyMemberHasAIChemUseValue(dialog_target, ptr->value); break; } int color; if (enabled) { if (index == selectedIndex) { color = colorTable[32747]; } else { color = colorTable[992]; } } else { color = colorTable[15855]; } const char* msg = getmsg(&custom_msg_file, &messageListItem, ptr->messageId); text_to_buf(dest + pitch * (text_height() * index + 42) + 42, msg, pitch - 84, pitch, color); } } } // 0x449FC0 static int gdCustomSelect(int a1) { int oldFont = text_curr(); CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 419, 0, 0, 0); Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle); if (backgroundFrm == NULL) { return -1; } int backgroundFrmWidth = art_frame_width(backgroundFrm, 0, 0); int backgroundFrmHeight = art_frame_length(backgroundFrm, 0, 0); int selectWindowX = (640 - backgroundFrmWidth) / 2; int selectWindowY = (480 - backgroundFrmHeight) / 2; int win = win_add(selectWindowX, selectWindowY, backgroundFrmWidth, backgroundFrmHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { art_ptr_unlock(backgroundFrmHandle); return -1; } unsigned char* windowBuffer = win_get_buf(win); unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0); buf_to_buf(backgroundFrmData, backgroundFrmWidth, backgroundFrmHeight, backgroundFrmWidth, windowBuffer, backgroundFrmWidth); art_ptr_unlock(backgroundFrmHandle); int btn1 = win_register_button(win, 70, 164, 14, 14, -1, -1, -1, KEY_RETURN, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (btn1 == -1) { win_delete(win); return -1; } int btn2 = win_register_button(win, 176, 163, 14, 14, -1, -1, -1, KEY_ESCAPE, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (btn2 == -1) { win_delete(win); return -1; } text_font(103); MessageListItem messageListItem; const char* msg; msg = getmsg(&custom_msg_file, &messageListItem, a1); text_to_buf(windowBuffer + backgroundFrmWidth * 15 + 40, msg, backgroundFrmWidth, backgroundFrmWidth, colorTable[18979]); msg = getmsg(&custom_msg_file, &messageListItem, 10); text_to_buf(windowBuffer + backgroundFrmWidth * 163 + 88, msg, backgroundFrmWidth, backgroundFrmWidth, colorTable[18979]); msg = getmsg(&custom_msg_file, &messageListItem, 11); text_to_buf(windowBuffer + backgroundFrmWidth * 162 + 193, msg, backgroundFrmWidth, backgroundFrmWidth, colorTable[18979]); int value = custom_current_selected[a1]; gdCustomSelectRedraw(windowBuffer, backgroundFrmWidth, a1, value); win_draw(win); int minX = selectWindowX + 42; int minY = selectWindowY + 42; int maxX = selectWindowX + backgroundFrmWidth - 42; int maxY = selectWindowY + backgroundFrmHeight - 42; bool done = false; unsigned int v53 = 0; while (!done) { int keyCode = get_input(); if (keyCode == -1) { continue; } if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { break; } if (keyCode == KEY_RETURN) { STRUCT_5189E4* ptr = &(custom_settings[a1][value]); custom_current_selected[a1] = value; gdCustomUpdateSetting(a1, ptr->value); done = true; } else if (keyCode == KEY_ESCAPE) { done = true; } else if (keyCode == -2) { if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { continue; } if (!mouse_click_in(minX, minY, maxX, maxY)) { continue; } int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int lineHeight = text_height(); int newValue = (mouseY - minY) / lineHeight; if (newValue >= 6) { continue; } unsigned int timestamp = get_time(); if (newValue == value) { if (elapsed_tocks(timestamp, v53) < 250) { custom_current_selected[a1] = newValue; gdCustomUpdateSetting(a1, newValue); done = true; } } else { STRUCT_5189E4* ptr = &(custom_settings[a1][newValue]); if (ptr->messageId != -1) { bool enabled = false; switch (a1) { case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE: enabled = partyMemberHasAIBurstValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE: enabled = partyMemberHasAIRunAwayValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON: enabled = partyMemberHasAIWeaponPrefValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE: enabled = partyMemberHasAIDistancePrefValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO: enabled = partyMemberHasAIAttackWhoValue(dialog_target, ptr->value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE: enabled = partyMemberHasAIChemUseValue(dialog_target, ptr->value); break; } if (enabled) { value = newValue; gdCustomSelectRedraw(windowBuffer, backgroundFrmWidth, a1, newValue); win_draw(win); } } } v53 = timestamp; } } win_delete(win); text_font(oldFont); return 0; } // 0x44A4E0 static void gdCustomUpdateSetting(int option, int value) { switch (option) { case PARTY_MEMBER_CUSTOMIZATION_OPTION_AREA_ATTACK_MODE: ai_set_burst_value(dialog_target, value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_RUN_AWAY_MODE: ai_set_run_away_value(dialog_target, value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_BEST_WEAPON: ai_set_weapon_pref_value(dialog_target, value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_DISTANCE: ai_set_distance_pref_value(dialog_target, value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_ATTACK_WHO: ai_set_attack_who_value(dialog_target, value); break; case PARTY_MEMBER_CUSTOMIZATION_OPTION_CHEM_USE: ai_set_chem_use_value(dialog_target, value); break; } } // 0x44A52C static void gdialog_barter_pressed(int btn, int keyCode) { if (PID_TYPE(dialog_target->pid) != OBJ_TYPE_CRITTER) { return; } Script* script; if (scr_ptr(dialog_target->sid, &script) == -1) { return; } Proto* proto; proto_ptr(dialog_target->pid, &proto); if (proto->critter.data.flags & CRITTER_BARTER) { if (gdialog_speech_playing) { if (soundPlaying(lip_info.sound)) { gdialogFreeSpeech(); } } dialogue_switch_mode = 2; dialogue_state = 4; // NOTE: Uninline. gdHide(); } else { MessageListItem messageListItem; // This person will not barter with you. messageListItem.num = 903; if (dialog_target_is_party) { // This critter can't carry anything. messageListItem.num = 913; } if (message_search(&proto_main_msg_file, &messageListItem)) { gdialogDisplayMsg(messageListItem.text); } else { debug_printf("\nError: gdialog: Can't find message!"); } } } // 0x44A62C static int gdialog_window_create() { const int screenWidth = GAME_DIALOG_WINDOW_WIDTH; if (gdialog_window_created) { return -1; } for (int index = 0; index < 9; index++) { gdialog_buttons[index] = -1; } CacheEntry* backgroundFrmHandle; // 389 - di_talkp.frm - dialog screen subwindow (party members) // 99 - di_talk.frm - dialog screen subwindow (NPC's) int backgroundFid = art_id(OBJ_TYPE_INTERFACE, dialog_target_is_party ? 389 : 99, 0, 0, 0); Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundFrmHandle); if (backgroundFrm == NULL) { return -1; } unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0); if (backgroundFrmData != NULL) { dialogue_subwin_len = art_frame_length(backgroundFrm, 0, 0); int dialogSubwindowX = 0; int dialogSubwindowY = 480 - dialogue_subwin_len; dialogueWindow = win_add(dialogSubwindowX, dialogSubwindowY, screenWidth, dialogue_subwin_len, 256, WINDOW_FLAG_0x02); if (dialogueWindow != -1) { unsigned char* v10 = win_get_buf(dialogueWindow); unsigned char* v14 = win_get_buf(dialogueBackWindow); // TODO: Not sure about offsets. buf_to_buf(v14 + screenWidth * (GAME_DIALOG_WINDOW_HEIGHT - dialogue_subwin_len), screenWidth, dialogue_subwin_len, screenWidth, v10, screenWidth); if (dialogue_just_started) { win_draw(dialogueBackWindow); gdialog_scroll_subwin(dialogueWindow, 1, backgroundFrmData, v10, 0, dialogue_subwin_len, -1); dialogue_just_started = 0; } else { gdialog_scroll_subwin(dialogueWindow, 1, backgroundFrmData, v10, 0, dialogue_subwin_len, 0); } art_ptr_unlock(backgroundFrmHandle); // BARTER/TRADE gdialog_buttons[0] = win_register_button(dialogueWindow, 593, 41, 14, 14, -1, -1, -1, -1, dialog_red_button_up_buf, dialog_red_button_down_buf, NULL, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[0] != -1) { win_register_button_func(gdialog_buttons[0], NULL, NULL, NULL, gdialog_barter_pressed); win_register_button_sound_func(gdialog_buttons[0], gsound_med_butt_press, gsound_med_butt_release); // di_rest1.frm - dialog rest button up int upFid = art_id(OBJ_TYPE_INTERFACE, 97, 0, 0, 0); unsigned char* reviewButtonUpData = art_ptr_lock_data(upFid, 0, 0, &gdialog_review_up_key); if (reviewButtonUpData != NULL) { // di_rest2.frm - dialog rest button down int downFid = art_id(OBJ_TYPE_INTERFACE, 98, 0, 0, 0); unsigned char* reivewButtonDownData = art_ptr_lock_data(downFid, 0, 0, &gdialog_review_down_key); if (reivewButtonDownData != NULL) { // REVIEW gdialog_buttons[1] = win_register_button(dialogueWindow, 13, 154, 51, 29, -1, -1, -1, -1, reviewButtonUpData, reivewButtonDownData, NULL, 0); if (gdialog_buttons[1] != -1) { win_register_button_func(gdialog_buttons[1], NULL, NULL, NULL, gdReviewPressed); win_register_button_sound_func(gdialog_buttons[1], gsound_red_butt_press, gsound_red_butt_release); if (!dialog_target_is_party) { gdialog_window_created = true; return 0; } // COMBAT CONTROL gdialog_buttons[2] = win_register_button(dialogueWindow, 593, 116, 14, 14, -1, -1, -1, -1, dialog_red_button_up_buf, dialog_red_button_down_buf, 0, BUTTON_FLAG_TRANSPARENT); if (gdialog_buttons[2] != -1) { win_register_button_func(gdialog_buttons[2], NULL, NULL, NULL, gdControlPressed); win_register_button_sound_func(gdialog_buttons[2], gsound_med_butt_press, gsound_med_butt_release); gdialog_window_created = true; return 0; } win_delete_button(gdialog_buttons[1]); gdialog_buttons[1] = -1; } art_ptr_unlock(gdialog_review_down_key); } art_ptr_unlock(gdialog_review_up_key); } win_delete_button(gdialog_buttons[0]); gdialog_buttons[0] = -1; } win_delete(dialogueWindow); dialogueWindow = -1; } } art_ptr_unlock(backgroundFrmHandle); return -1; } // 0x44A9D8 static void gdialog_window_destroy() { if (dialogueWindow == -1) { return; } for (int index = 0; index < 9; index++) { win_delete_button(gdialog_buttons[index]); gdialog_buttons[index] = -1; } art_ptr_unlock(gdialog_review_down_key); art_ptr_unlock(gdialog_review_up_key); int offset = (GAME_DIALOG_WINDOW_WIDTH) * (480 - dialogue_subwin_len); unsigned char* backgroundWindowBuffer = win_get_buf(dialogueBackWindow) + offset; int frmId; if (dialog_target_is_party) { // di_talkp.frm - dialog screen subwindow (party members) frmId = 389; } else { // di_talk.frm - dialog screen subwindow (NPC's) frmId = 99; } CacheEntry* backgroundFrmHandle; int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { unsigned char* windowBuffer = win_get_buf(dialogueWindow); gdialog_scroll_subwin(dialogueWindow, 0, backgroundFrmData, windowBuffer, backgroundWindowBuffer, dialogue_subwin_len, 0); art_ptr_unlock(backgroundFrmHandle); win_delete(dialogueWindow); gdialog_window_created = 0; dialogueWindow = -1; } } // NOTE: Inlined. // // 0x44AAD8 static int talk_to_create_background_window() { dialogueBackWindow = win_add(0, 0, scr_size.lrx - scr_size.ulx + 1, GAME_DIALOG_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x02); if (dialogueBackWindow != -1) { return 0; } return -1; } // 0x44AB18 static int talk_to_refresh_background_window() { CacheEntry* backgroundFrmHandle; // alltlk.frm - dialog screen background int fid = art_id(OBJ_TYPE_INTERFACE, 103, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData == NULL) { return -1; } int windowWidth = GAME_DIALOG_WINDOW_WIDTH; unsigned char* windowBuffer = win_get_buf(dialogueBackWindow); buf_to_buf(backgroundFrmData, windowWidth, 480, windowWidth, windowBuffer, windowWidth); art_ptr_unlock(backgroundFrmHandle); if (!dialogue_just_started) { win_draw(dialogueBackWindow); } return 0; } // 0x44ABA8 static int talkToRefreshDialogWindowRect(Rect* rect) { int frmId; if (dialog_target_is_party) { // di_talkp.frm - dialog screen subwindow (party members) frmId = 389; } else { // di_talk.frm - dialog screen subwindow (NPC's) frmId = 99; } CacheEntry* backgroundFrmHandle; int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData == NULL) { return -1; } int offset = 640 * rect->uly + rect->ulx; unsigned char* windowBuffer = win_get_buf(dialogueWindow); buf_to_buf(backgroundFrmData + offset, rect->lrx - rect->ulx, rect->lry - rect->uly, GAME_DIALOG_WINDOW_WIDTH, windowBuffer + offset, GAME_DIALOG_WINDOW_WIDTH); art_ptr_unlock(backgroundFrmHandle); win_draw_rect(dialogueWindow, rect); return 0; } // 0x44AC68 static void talk_to_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, unsigned char* a9, unsigned char* a10) { int srcStep = srcPitch - srcWidth; int destStep = destPitch - srcWidth; dest += destPitch * destY + destX; for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { unsigned char v1 = *src++; if (v1 != 0) { v1 = (256 - v1) >> 4; } unsigned char v15 = *dest; *dest++ = a9[256 * v1 + v15]; } src += srcStep; dest += destStep; } } // 0x44ACFC static void gdDisplayFrame(Art* headFrm, int frame) { // 0x518BF4 static int totalHotx = 0; if (dialogueWindow == -1) { return; } if (headFrm != NULL) { if (frame == 0) { totalHotx = 0; } int backgroundFid = art_id(OBJ_TYPE_BACKGROUND, backgroundIndex, 0, 0, 0); CacheEntry* backgroundHandle; Art* backgroundFrm = art_ptr_lock(backgroundFid, &backgroundHandle); if (backgroundFrm == NULL) { debug_printf("\tError locking background in display...\n"); } unsigned char* backgroundFrmData = art_frame_data(backgroundFrm, 0, 0); if (backgroundFrmData != NULL) { buf_to_buf(backgroundFrmData, 388, 200, 388, headWindowBuffer, GAME_DIALOG_WINDOW_WIDTH); } else { debug_printf("\tError getting background data in display...\n"); } art_ptr_unlock(backgroundHandle); int width = art_frame_width(headFrm, frame, 0); int height = art_frame_length(headFrm, frame, 0); unsigned char* data = art_frame_data(headFrm, frame, 0); int a3; int v8; art_frame_offset(headFrm, 0, &a3, &v8); int a4; int a5; art_frame_hot(headFrm, frame, 0, &a4, &a5); totalHotx += a4; a3 += totalHotx; if (data != NULL) { int destWidth = GAME_DIALOG_WINDOW_WIDTH; int destOffset = destWidth * (200 - height) + a3 + (388 - width) / 2; if (destOffset + width * v8 > 0) { destOffset += width * v8; } trans_buf_to_buf( data, width, height, width, headWindowBuffer + destOffset, destWidth); } else { debug_printf("\tError getting head data in display...\n"); } } else { if (talk_need_to_center == 1) { talk_need_to_center = 0; tile_refresh_display(); } unsigned char* src = win_get_buf(display_win); buf_to_buf( src + ((scr_size.lry - scr_size.uly + 1 - 332) / 2) * (GAME_DIALOG_WINDOW_WIDTH) + (GAME_DIALOG_WINDOW_WIDTH - 388) / 2, 388, 200, scr_size.lrx - scr_size.ulx + 1, headWindowBuffer, GAME_DIALOG_WINDOW_WIDTH); } Rect v27; v27.ulx = 126; v27.uly = 14; v27.lrx = 514; v27.lry = 214; unsigned char* dest = win_get_buf(dialogueBackWindow); unsigned char* data1 = art_frame_data(upper_hi_fp, 0, 0); talk_to_translucent_trans_buf_to_buf(data1, upper_hi_wid, upper_hi_len, upper_hi_wid, dest, 426, 15, GAME_DIALOG_WINDOW_WIDTH, light_BlendTable, light_GrayTable); unsigned char* data2 = art_frame_data(lower_hi_fp, 0, 0); talk_to_translucent_trans_buf_to_buf(data2, lower_hi_wid, lower_hi_len, lower_hi_wid, dest, 129, 214 - lower_hi_len - 2, GAME_DIALOG_WINDOW_WIDTH, dark_BlendTable, dark_GrayTable); for (int index = 0; index < 8; ++index) { Rect* rect = &(backgrndRects[index]); int width = rect->lrx - rect->ulx; trans_buf_to_buf(backgrndBufs[index], width, rect->lry - rect->uly, width, dest + (GAME_DIALOG_WINDOW_WIDTH) * rect->uly + rect->ulx, GAME_DIALOG_WINDOW_WIDTH); } win_draw_rect(dialogueBackWindow, &v27); } // 0x44B080 static void gdBlendTableInit() { for (int color = 0; color < 256; color++) { int r = (Color2RGB(color) & 0x7C00) >> 10; int g = (Color2RGB(color) & 0x3E0) >> 5; int b = Color2RGB(color) & 0x1F; light_GrayTable[color] = ((r + 2 * g + 2 * b) / 10) >> 2; dark_GrayTable[color] = ((r + g + b) / 10) >> 2; } light_GrayTable[0] = 0; dark_GrayTable[0] = 0; light_BlendTable = getColorBlendTable(colorTable[17969]); dark_BlendTable = getColorBlendTable(colorTable[22187]); // hilight1.frm - dialogue upper hilight int upperHighlightFid = art_id(OBJ_TYPE_INTERFACE, 115, 0, 0, 0); upper_hi_fp = art_ptr_lock(upperHighlightFid, &upper_hi_key); upper_hi_wid = art_frame_width(upper_hi_fp, 0, 0); upper_hi_len = art_frame_length(upper_hi_fp, 0, 0); // hilight2.frm - dialogue lower hilight int lowerHighlightFid = art_id(OBJ_TYPE_INTERFACE, 116, 0, 0, 0); lower_hi_fp = art_ptr_lock(lowerHighlightFid, &lower_hi_key); lower_hi_wid = art_frame_width(lower_hi_fp, 0, 0); lower_hi_len = art_frame_length(lower_hi_fp, 0, 0); } // NOTE: Inlined. // // 0x44B1D4 static void gdBlendTableExit() { freeColorBlendTable(colorTable[17969]); freeColorBlendTable(colorTable[22187]); art_ptr_unlock(upper_hi_key); art_ptr_unlock(lower_hi_key); } ================================================ FILE: src/game/gdialog.h ================================================ #ifndef FALLOUT_GAME_GDIALOG_H_ #define FALLOUT_GAME_GDIALOG_H_ #include #include "game/art.h" #include "int/intrpret.h" #include "game/object_types.h" extern unsigned char* light_BlendTable; extern unsigned char* dark_BlendTable; extern Object* dialog_target; extern bool dialog_target_is_party; extern int dialogue_head; extern int dialogue_scr_id; extern unsigned char light_GrayTable[256]; extern unsigned char dark_GrayTable[256]; int gdialogInit(); int gdialogReset(); int gdialogExit(); bool gdialogActive(); void gdialogEnter(Object* a1, int a2); void gdialogSystemEnter(); void gdialogSetupSpeech(const char* a1); void gdialogFreeSpeech(); int gdialogEnableBK(); int gdialogDisableBK(); int gdialogInitFromScript(int headFid, int reaction); int gdialogExitFromScript(); void gdialogSetBackground(int a1); void gdialogDisplayMsg(char* msg); int gdialogStart(); int gdialogSayMessage(); int gdialogOption(int messageListId, int messageId, const char* a3, int reaction); int gdialogOptionStr(int messageListId, const char* text, const char* a3, int reaction); int gdialogOptionProc(int messageListId, int messageId, int proc, int reaction); int gdialogOptionProcStr(int messageListId, const char* text, int proc, int reaction); int gdialogReply(Program* a1, int a2, int a3); int gdialogReplyStr(Program* a1, int a2, const char* a3); int gdialogGo(); void gdialogUpdatePartyStatus(); void talk_to_critter_reacts(int a1); void gdialogSetBarterMod(int modifier); int gdActivateBarter(int modifier); void barter_end_to_talk_to(); #endif /* FALLOUT_GAME_GDIALOG_H_ */ ================================================ FILE: src/game/gmemory.c ================================================ #include "game/gmemory.h" #include "plib/db/db.h" #include "plib/assoc/assoc.h" #include "plib/gnw/memory.h" #include "int/memdbg.h" // NOTE: Unused. // // 0x44B200 void* localmymalloc(size_t size) { return mymalloc(size, __FILE__, __LINE__); // "gmemory.c", 22 } // NOTE: Unused. // // 0x44B214 void* localmyrealloc(void* ptr, size_t size) { return myrealloc(ptr, size, __FILE__, __LINE__); // "gmemory.c", 26 } // NOTE: Unused. // // 0x44B228 void localmyfree(void* ptr) { myfree(ptr, __FILE__, __LINE__); // "gmemory.c", 30 } // NOTE: Unused. // // 0x44B23C char* localmystrdup(const char* string) { return mystrdup(string, __FILE__, __LINE__); // "gmemory.c", 34 } // 0x44B250 int gmemory_init() { assoc_register_mem(mem_malloc, mem_realloc, mem_free); db_register_mem(mem_malloc, mem_strdup, mem_free); memoryRegisterAlloc(gmalloc, grealloc, gfree); return 0; } // 0x44B294 void* gmalloc(size_t size) { return mem_malloc(size); } // 0x44B29C void* grealloc(void* ptr, size_t newSize) { return mem_realloc(ptr, newSize); } // 0x44B2A4 void gfree(void* ptr) { mem_free(ptr); } ================================================ FILE: src/game/gmemory.h ================================================ #ifndef FALLOUT_GAME_GMEMORY_H_ #define FALLOUT_GAME_GMEMORY_H_ #include void* localmymalloc(size_t size); void* localmyrealloc(void* ptr, size_t size); void localmyfree(void* ptr); char* localmystrdup(const char* string); int gmemory_init(); void* gmalloc(size_t size); void* grealloc(void* ptr, size_t newSize); void gfree(void* ptr); #endif /* FALLOUT_GAME_GMEMORY_H_ */ ================================================ FILE: src/game/gmouse.c ================================================ #include "game/gmouse.h" #include #include #include #include "game/art.h" #include "game/actions.h" #include "plib/color/color.h" #include "game/combat.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "game/object.h" #include "game/proto.h" #include "game/protinst.h" #include "game/skilldex.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" typedef enum ScrollableDirections { SCROLLABLE_W = 0x01, SCROLLABLE_E = 0x02, SCROLLABLE_N = 0x04, SCROLLABLE_S = 0x08, } ScrollableDirections; static int gmouse_3d_init(); static int gmouse_3d_reset(); static void gmouse_3d_exit(); static int gmouse_3d_lock_frames(); static void gmouse_3d_unlock_frames(); static int gmouse_3d_set_flat_fid(int fid, Rect* rect); static int gmouse_3d_reset_flat_fid(Rect* rect); static int gmouse_3d_move_to(int x, int y, int elevation, Rect* a4); static int gmouse_check_scrolling(int x, int y, int cursor); static int gmObjIsValidTarget(Object* object); // 0x518BF8 static bool gmouse_initialized = false; // 0x518BFC static int gmouse_enabled = 0; // 0x518C00 static int gmouse_mapper_mode = 0; // 0x518C04 static int gmouse_click_to_scroll = 0; // 0x518C08 static int gmouse_scrolling_enabled = 1; // 0x518C0C static int gmouse_current_cursor = MOUSE_CURSOR_NONE; // 0x518C10 static CacheEntry* gmouse_current_cursor_key = INVALID_CACHE_ENTRY; // 0x518C14 static int gmouse_cursor_nums[MOUSE_CURSOR_TYPE_COUNT] = { 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 330, 331, 329, 328, 332, 334, 333, 335, 279, 280, 281, 293, 310, 278, 295, }; // 0x518C80 static bool gmouse_3d_initialized = false; // 0x518C84 static bool gmouse_3d_hover_test = false; // 0x518C88 static unsigned int gmouse_3d_last_move_time = 0; // actmenu.frm // 0x518C8C static Art* gmouse_3d_menu_frame = NULL; // 0x518C90 static CacheEntry* gmouse_3d_menu_frame_key = INVALID_CACHE_ENTRY; // 0x518C94 static int gmouse_3d_menu_frame_width = 0; // 0x518C98 static int gmouse_3d_menu_frame_height = 0; // 0x518C9C static int gmouse_3d_menu_frame_size = 0; // 0x518CA0 static int gmouse_3d_menu_frame_hot_x = 0; // 0x518CA4 static int gmouse_3d_menu_frame_hot_y = 0; // 0x518CA8 static unsigned char* gmouse_3d_menu_frame_data = NULL; // actpick.frm // 0x518CAC static Art* gmouse_3d_pick_frame = NULL; // 0x518CB0 static CacheEntry* gmouse_3d_pick_frame_key = INVALID_CACHE_ENTRY; // 0x518CB4 static int gmouse_3d_pick_frame_width = 0; // 0x518CB8 static int gmouse_3d_pick_frame_height = 0; // 0x518CBC static int gmouse_3d_pick_frame_size = 0; // 0x518CC0 static int gmouse_3d_pick_frame_hot_x = 0; // 0x518CC4 static int gmouse_3d_pick_frame_hot_y = 0; // 0x518CC8 static unsigned char* gmouse_3d_pick_frame_data = NULL; // acttohit.frm // 0x518CCC static Art* gmouse_3d_to_hit_frame = NULL; // 0x518CD0 static CacheEntry* gmouse_3d_to_hit_frame_key = INVALID_CACHE_ENTRY; // 0x518CD4 static int gmouse_3d_to_hit_frame_width = 0; // 0x518CD8 static int gmouse_3d_to_hit_frame_height = 0; // 0x518CDC static int gmouse_3d_to_hit_frame_size = 0; // 0x518CE0 static unsigned char* gmouse_3d_to_hit_frame_data = NULL; // blank.frm // 0x518CE4 static Art* gmouse_3d_hex_base_frame = NULL; // 0x518CE8 static CacheEntry* gmouse_3d_hex_base_frame_key = INVALID_CACHE_ENTRY; // 0x518CEC static int gmouse_3d_hex_base_frame_width = 0; // 0x518CF0 static int gmouse_3d_hex_base_frame_height = 0; // 0x518CF4 static int gmouse_3d_hex_base_frame_size = 0; // 0x518CF8 static unsigned char* gmouse_3d_hex_base_frame_data = NULL; // msef000.frm // 0x518CFC static Art* gmouse_3d_hex_frame = NULL; // 0x518D00 static CacheEntry* gmouse_3d_hex_frame_key = INVALID_CACHE_ENTRY; // 0x518D04 static int gmouse_3d_hex_frame_width = 0; // 0x518D08 static int gmouse_3d_hex_frame_height = 0; // 0x518D0C static int gmouse_3d_hex_frame_size = 0; // 0x518D10 static unsigned char* gmouse_3d_hex_frame_data = NULL; // 0x518D14 static unsigned char gmouse_3d_menu_available_actions = 0; // 0x518D18 static unsigned char* gmouse_3d_menu_actions_start = NULL; // 0x518D1C static unsigned char gmouse_3d_menu_current_action_index = 0; // 0x518D1E static short gmouse_3d_action_nums[GAME_MOUSE_ACTION_MENU_ITEM_COUNT] = { 253, // Cancel 255, // Drop 257, // Inventory 259, // Look 261, // Rotate 263, // Talk 265, // Use/Get 302, // Unload 304, // Skill 435, // Push }; // 0x518D34 static int gmouse_3d_modes_enabled = 1; // 0x518D38 static int gmouse_3d_current_mode = GAME_MOUSE_MODE_MOVE; // 0x518D3C static int gmouse_3d_mode_nums[GAME_MOUSE_MODE_COUNT] = { 249, 250, 251, 293, 293, 293, 293, 293, 293, 293, 293, }; // 0x518D68 static int gmouse_skill_table[GAME_MOUSE_MODE_SKILL_COUNT] = { SKILL_FIRST_AID, SKILL_DOCTOR, SKILL_LOCKPICK, SKILL_STEAL, SKILL_TRAPS, SKILL_SCIENCE, SKILL_REPAIR, }; // 0x518D84 static int gmouse_wait_cursor_frame = 0; // 0x518D88 static unsigned int gmouse_wait_cursor_time = 0; // 0x518D8C static int gmouse_bk_last_cursor = -1; // 0x518D90 static bool gmouse_3d_item_highlight = true; // 0x518D94 static Object* outlined_object = NULL; // 0x518D98 bool gmouse_clicked_on_edge = false; // 0x596C3C static int gmouse_3d_menu_frame_actions[GAME_MOUSE_ACTION_MENU_ITEM_COUNT]; // 0x596C64 static int gmouse_3d_last_mouse_x; // 0x596C68 static int gmouse_3d_last_mouse_y; // blank.frm // 0x596C6C Object* obj_mouse; // msef000.frm // 0x596C70 Object* obj_mouse_flat; // 0x44B2B0 int gmouse_init() { if (gmouse_initialized) { return -1; } if (gmouse_3d_init() != 0) { return -1; } gmouse_initialized = true; gmouse_enabled = 1; gmouse_set_cursor(MOUSE_CURSOR_ARROW); return 0; } // 0x44B2E8 int gmouse_reset() { if (!gmouse_initialized) { return -1; } // NOTE: Uninline. if (gmouse_3d_reset() != 0) { return -1; } // NOTE: Uninline. gmouse_enable(); gmouse_scrolling_enabled = 1; gmouse_set_cursor(MOUSE_CURSOR_ARROW); gmouse_wait_cursor_frame = 0; gmouse_wait_cursor_time = 0; gmouse_clicked_on_edge = 0; return 0; } // 0x44B3B8 void gmouse_exit() { if (!gmouse_initialized) { return; } mouse_hide(); mouse_set_shape(NULL, 0, 0, 0, 0, 0, 0); // NOTE: Uninline. gmouse_3d_exit(); if (gmouse_current_cursor_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_current_cursor_key); } gmouse_current_cursor_key = INVALID_CACHE_ENTRY; gmouse_enabled = 0; gmouse_initialized = false; gmouse_current_cursor = -1; } // 0x44B454 void gmouse_enable() { if (!gmouse_enabled) { gmouse_current_cursor = -1; gmouse_set_cursor(MOUSE_CURSOR_NONE); gmouse_scrolling_enabled = 1; gmouse_enabled = 1; gmouse_bk_last_cursor = -1; } } // 0x44B48C void gmouse_disable(int a1) { if (gmouse_enabled) { gmouse_set_cursor(MOUSE_CURSOR_NONE); gmouse_enabled = 0; if (a1 & 1) { gmouse_scrolling_enabled = 1; } else { gmouse_scrolling_enabled = 0; } } } // NOTE: Unused. // // 0x44B4C4 int gmouse_is_enabled() { return gmouse_enabled; } // 0x44B4CC void gmouse_enable_scrolling() { gmouse_scrolling_enabled = 1; } // 0x44B4D8 void gmouse_disable_scrolling() { gmouse_scrolling_enabled = 0; } // NOTE: Inlined. // // 0x44B4E4 int gmouse_scrolling_is_enabled() { return gmouse_scrolling_enabled; } // NOTE: Unused. // // 0x44B4EC void gmouse_set_click_to_scroll(int a1) { if (a1 != gmouse_click_to_scroll) { gmouse_click_to_scroll = a1; gmouse_clicked_on_edge = 0; } } // 0x44B504 int gmouse_get_click_to_scroll() { return gmouse_click_to_scroll; } // 0x44B54C int gmouse_is_scrolling() { int v1 = 0; if (gmouse_scrolling_enabled) { int x; int y; mouse_get_position(&x, &y); if (x == scr_size.ulx || x == scr_size.lrx || y == scr_size.uly || y == scr_size.lry) { switch (gmouse_current_cursor) { case MOUSE_CURSOR_SCROLL_NW: case MOUSE_CURSOR_SCROLL_N: case MOUSE_CURSOR_SCROLL_NE: case MOUSE_CURSOR_SCROLL_E: case MOUSE_CURSOR_SCROLL_SE: case MOUSE_CURSOR_SCROLL_S: case MOUSE_CURSOR_SCROLL_SW: case MOUSE_CURSOR_SCROLL_W: case MOUSE_CURSOR_SCROLL_NW_INVALID: case MOUSE_CURSOR_SCROLL_N_INVALID: case MOUSE_CURSOR_SCROLL_NE_INVALID: case MOUSE_CURSOR_SCROLL_E_INVALID: case MOUSE_CURSOR_SCROLL_SE_INVALID: case MOUSE_CURSOR_SCROLL_S_INVALID: case MOUSE_CURSOR_SCROLL_SW_INVALID: case MOUSE_CURSOR_SCROLL_W_INVALID: v1 = 1; break; default: return v1; } } } return v1; } // 0x44B684 void gmouse_bk_process() { // 0x596C74 static Object* last_object; // 0x518D9C static int last_tile = -1; if (!gmouse_initialized) { return; } int mouseX; int mouseY; if (gmouse_current_cursor >= FIRST_GAME_MOUSE_ANIMATED_CURSOR) { mouse_info(); // NOTE: Uninline. if (gmouse_scrolling_is_enabled()) { mouse_get_position(&mouseX, &mouseY); int oldMouseCursor = gmouse_current_cursor; if (gmouse_check_scrolling(mouseX, mouseY, gmouse_current_cursor) == 0) { switch (oldMouseCursor) { case MOUSE_CURSOR_SCROLL_NW: case MOUSE_CURSOR_SCROLL_N: case MOUSE_CURSOR_SCROLL_NE: case MOUSE_CURSOR_SCROLL_E: case MOUSE_CURSOR_SCROLL_SE: case MOUSE_CURSOR_SCROLL_S: case MOUSE_CURSOR_SCROLL_SW: case MOUSE_CURSOR_SCROLL_W: case MOUSE_CURSOR_SCROLL_NW_INVALID: case MOUSE_CURSOR_SCROLL_N_INVALID: case MOUSE_CURSOR_SCROLL_NE_INVALID: case MOUSE_CURSOR_SCROLL_E_INVALID: case MOUSE_CURSOR_SCROLL_SE_INVALID: case MOUSE_CURSOR_SCROLL_S_INVALID: case MOUSE_CURSOR_SCROLL_SW_INVALID: case MOUSE_CURSOR_SCROLL_W_INVALID: break; default: gmouse_bk_last_cursor = oldMouseCursor; break; } return; } if (gmouse_bk_last_cursor != -1) { gmouse_set_cursor(gmouse_bk_last_cursor); gmouse_bk_last_cursor = -1; return; } } gmouse_set_cursor(gmouse_current_cursor); return; } if (!gmouse_enabled) { // NOTE: Uninline. if (gmouse_scrolling_is_enabled()) { mouse_get_position(&mouseX, &mouseY); int oldMouseCursor = gmouse_current_cursor; if (gmouse_check_scrolling(mouseX, mouseY, gmouse_current_cursor) == 0) { switch (oldMouseCursor) { case MOUSE_CURSOR_SCROLL_NW: case MOUSE_CURSOR_SCROLL_N: case MOUSE_CURSOR_SCROLL_NE: case MOUSE_CURSOR_SCROLL_E: case MOUSE_CURSOR_SCROLL_SE: case MOUSE_CURSOR_SCROLL_S: case MOUSE_CURSOR_SCROLL_SW: case MOUSE_CURSOR_SCROLL_W: case MOUSE_CURSOR_SCROLL_NW_INVALID: case MOUSE_CURSOR_SCROLL_N_INVALID: case MOUSE_CURSOR_SCROLL_NE_INVALID: case MOUSE_CURSOR_SCROLL_E_INVALID: case MOUSE_CURSOR_SCROLL_SE_INVALID: case MOUSE_CURSOR_SCROLL_S_INVALID: case MOUSE_CURSOR_SCROLL_SW_INVALID: case MOUSE_CURSOR_SCROLL_W_INVALID: break; default: gmouse_bk_last_cursor = oldMouseCursor; break; } return; } if (gmouse_bk_last_cursor != -1) { gmouse_set_cursor(gmouse_bk_last_cursor); gmouse_bk_last_cursor = -1; } } return; } mouse_get_position(&mouseX, &mouseY); int oldMouseCursor = gmouse_current_cursor; if (gmouse_check_scrolling(mouseX, mouseY, MOUSE_CURSOR_NONE) == 0) { switch (oldMouseCursor) { case MOUSE_CURSOR_SCROLL_NW: case MOUSE_CURSOR_SCROLL_N: case MOUSE_CURSOR_SCROLL_NE: case MOUSE_CURSOR_SCROLL_E: case MOUSE_CURSOR_SCROLL_SE: case MOUSE_CURSOR_SCROLL_S: case MOUSE_CURSOR_SCROLL_SW: case MOUSE_CURSOR_SCROLL_W: case MOUSE_CURSOR_SCROLL_NW_INVALID: case MOUSE_CURSOR_SCROLL_N_INVALID: case MOUSE_CURSOR_SCROLL_NE_INVALID: case MOUSE_CURSOR_SCROLL_E_INVALID: case MOUSE_CURSOR_SCROLL_SE_INVALID: case MOUSE_CURSOR_SCROLL_S_INVALID: case MOUSE_CURSOR_SCROLL_SW_INVALID: case MOUSE_CURSOR_SCROLL_W_INVALID: break; default: gmouse_bk_last_cursor = oldMouseCursor; break; } return; } if (gmouse_bk_last_cursor != -1) { gmouse_set_cursor(gmouse_bk_last_cursor); gmouse_bk_last_cursor = -1; } if (win_get_top_win(mouseX, mouseY) != display_win) { if (gmouse_current_cursor == MOUSE_CURSOR_NONE) { gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); if (gmouse_3d_current_mode >= 2 && !isInCombat()) { gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); } } return; } // NOTE: Strange set of conditions and jumps. Not sure about this one. switch (gmouse_current_cursor) { case MOUSE_CURSOR_NONE: case MOUSE_CURSOR_ARROW: case MOUSE_CURSOR_SMALL_ARROW_UP: case MOUSE_CURSOR_SMALL_ARROW_DOWN: case MOUSE_CURSOR_CROSSHAIR: case MOUSE_CURSOR_USE_CROSSHAIR: if (gmouse_current_cursor != MOUSE_CURSOR_NONE) { gmouse_set_cursor(MOUSE_CURSOR_NONE); } if ((obj_mouse_flat->flags & OBJECT_HIDDEN) != 0) { gmouse_3d_on(); } break; } Rect r1; if (gmouse_3d_move_to(mouseX, mouseY, map_elevation, &r1) == 0) { tile_refresh_rect(&r1, map_elevation); } if ((obj_mouse_flat->flags & OBJECT_HIDDEN) != 0 || gmouse_mapper_mode != 0) { return; } unsigned int v3 = get_bk_time(); if (mouseX == gmouse_3d_last_mouse_x && mouseY == gmouse_3d_last_mouse_y) { if (gmouse_3d_hover_test || elapsed_tocks(v3, gmouse_3d_last_move_time) < 250) { return; } if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) { if (gmouse_3d_current_mode == GAME_MOUSE_MODE_ARROW) { gmouse_3d_last_move_time = v3; gmouse_3d_hover_test = true; Object* pointedObject = object_under_mouse(-1, true, map_elevation); if (pointedObject != NULL) { int primaryAction = -1; switch (FID_TYPE(pointedObject->fid)) { case OBJ_TYPE_ITEM: primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE; if (gmouse_3d_item_highlight) { Rect tmp; if (obj_outline_object(pointedObject, OUTLINE_TYPE_ITEM, &tmp) == 0) { tile_refresh_rect(&tmp, map_elevation); outlined_object = pointedObject; } } break; case OBJ_TYPE_CRITTER: if (pointedObject == obj_dude) { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_ROTATE; } else { if (obj_action_can_talk_to(pointedObject)) { if (isInCombat()) { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; } else { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_TALK; } } else { if (critter_flag_check(pointedObject->pid, CRITTER_NO_STEAL)) { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; } else { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE; } } } break; case OBJ_TYPE_SCENERY: if (!obj_action_can_use(pointedObject)) { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; } else { primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_USE; } break; case OBJ_TYPE_WALL: primaryAction = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; break; } if (primaryAction != -1) { if (gmouse_3d_build_pick_frame(mouseX, mouseY, primaryAction, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99) == 0) { Rect tmp; int fid = art_id(OBJ_TYPE_INTERFACE, 282, 0, 0, 0); // NOTE: Uninline. if (gmouse_3d_set_flat_fid(fid, &tmp) == 0) { tile_refresh_rect(&tmp, map_elevation); } } } if (pointedObject != last_object) { last_object = pointedObject; obj_look_at(obj_dude, last_object); } } } else if (gmouse_3d_current_mode == GAME_MOUSE_MODE_CROSSHAIR) { Object* pointedObject = object_under_mouse(OBJ_TYPE_CRITTER, false, map_elevation); if (pointedObject == NULL) { pointedObject = object_under_mouse(-1, false, map_elevation); if (!gmObjIsValidTarget(pointedObject)) { pointedObject = NULL; } } if (pointedObject != NULL) { bool pointedObjectIsCritter = FID_TYPE(pointedObject->fid) == OBJ_TYPE_CRITTER; int combatLooks = 0; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, &combatLooks); if (combatLooks != 0) { if (obj_examine(obj_dude, pointedObject) == -1) { obj_look_at(obj_dude, pointedObject); } } int color; int accuracy; char formattedAccuracy[8]; if (combat_to_hit(pointedObject, &accuracy)) { sprintf(formattedAccuracy, "%d%%", accuracy); if (pointedObjectIsCritter) { if (pointedObject->data.critter.combat.team != 0) { color = colorTable[32767]; } else { color = colorTable[32495]; } } else { color = colorTable[17969]; } } else { sprintf(formattedAccuracy, " %c ", 'X'); if (pointedObjectIsCritter) { if (pointedObject->data.critter.combat.team != 0) { color = colorTable[31744]; } else { color = colorTable[18161]; } } else { color = colorTable[32239]; } } if (gmouse_3d_build_to_hit_frame(formattedAccuracy, color) == 0) { Rect tmp; int fid = art_id(OBJ_TYPE_INTERFACE, 284, 0, 0, 0); // NOTE: Uninline. if (gmouse_3d_set_flat_fid(fid, &tmp) == 0) { tile_refresh_rect(&tmp, map_elevation); } } if (last_object != pointedObject) { last_object = pointedObject; } } else { Rect tmp; if (gmouse_3d_reset_flat_fid(&tmp) == 0) { tile_refresh_rect(&tmp, map_elevation); } } gmouse_3d_last_move_time = v3; gmouse_3d_hover_test = true; } return; } char formattedActionPoints[8]; int color; int v6 = make_path(obj_dude, obj_dude->tile, obj_mouse_flat->tile, NULL, 1); if (v6) { if (!isInCombat()) { formattedActionPoints[0] = '\0'; color = colorTable[31744]; } else { int v7 = critter_compute_ap_from_distance(obj_dude, v6); int v8; if (v7 - combat_free_move >= 0) { v8 = v7 - combat_free_move; } else { v8 = 0; } if (v8 <= obj_dude->data.critter.combat.ap) { sprintf(formattedActionPoints, "%d", v8); color = colorTable[32767]; } else { sprintf(formattedActionPoints, "%c", 'X'); color = colorTable[31744]; } } } else { sprintf(formattedActionPoints, "%c", 'X'); color = colorTable[31744]; } if (gmouse_3d_build_hex_frame(formattedActionPoints, color) == 0) { Rect tmp; obj_bound(obj_mouse_flat, &tmp); tile_refresh_rect(&tmp, 0); } gmouse_3d_last_move_time = v3; gmouse_3d_hover_test = true; last_tile = obj_mouse_flat->tile; return; } gmouse_3d_last_move_time = v3; gmouse_3d_hover_test = false; gmouse_3d_last_mouse_x = mouseX; gmouse_3d_last_mouse_y = mouseY; if (!gmouse_mapper_mode) { int fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0); gmouse_3d_set_fid(fid); } int v34 = 0; Rect r2; Rect r26; if (gmouse_3d_reset_flat_fid(&r2) == 0) { v34 |= 1; } if (outlined_object != NULL) { if (obj_remove_outline(outlined_object, &r26) == 0) { v34 |= 2; } outlined_object = NULL; } switch (v34) { case 3: rect_min_bound(&r2, &r26, &r2); // FALLTHROUGH case 1: tile_refresh_rect(&r2, map_elevation); break; case 2: tile_refresh_rect(&r26, map_elevation); break; } } // 0x44BFA8 void gmouse_handle_event(int mouseX, int mouseY, int mouseState) { if (!gmouse_initialized) { return; } if (gmouse_current_cursor >= MOUSE_CURSOR_WAIT_PLANET) { return; } if (!gmouse_enabled) { return; } if (gmouse_clicked_on_edge) { if (gmouse_get_click_to_scroll()) { return; } } if (!mouse_click_in(0, 0, scr_size.lrx - scr_size.ulx, scr_size.lry - scr_size.uly - 100)) { return; } if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if ((mouseState & MOUSE_EVENT_RIGHT_BUTTON_REPEAT) == 0 && (obj_mouse_flat->flags & OBJECT_HIDDEN) == 0) { gmouse_3d_toggle_mode(); } return; } if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { if (gmouse_3d_current_mode == GAME_MOUSE_MODE_MOVE) { int actionPoints; if (isInCombat()) { actionPoints = combat_free_move + obj_dude->data.critter.combat.ap; } else { actionPoints = -1; } bool running; configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, &running); if (keys[DIK_LSHIFT] || keys[DIK_RSHIFT]) { if (running) { dude_move(actionPoints); return; } } else { if (!running) { dude_move(actionPoints); return; } } dude_run(actionPoints); return; } if (gmouse_3d_current_mode == GAME_MOUSE_MODE_ARROW) { Object* v5 = object_under_mouse(-1, true, map_elevation); if (v5 != NULL) { switch (FID_TYPE(v5->fid)) { case OBJ_TYPE_ITEM: action_get_an_object(obj_dude, v5); break; case OBJ_TYPE_CRITTER: if (v5 == obj_dude) { if (FID_ANIM_TYPE(obj_dude->fid) == ANIM_STAND) { Rect a1; if (obj_inc_rotation(v5, &a1) == 0) { tile_refresh_rect(&a1, v5->elevation); } } } else { if (obj_action_can_talk_to(v5)) { if (isInCombat()) { if (obj_examine(obj_dude, v5) == -1) { obj_look_at(obj_dude, v5); } } else { action_talk_to(obj_dude, v5); } } else { action_loot_container(obj_dude, v5); } } break; case OBJ_TYPE_SCENERY: if (obj_action_can_use(v5)) { action_use_an_object(obj_dude, v5); } else { if (obj_examine(obj_dude, v5) == -1) { obj_look_at(obj_dude, v5); } } break; case OBJ_TYPE_WALL: if (obj_examine(obj_dude, v5) == -1) { obj_look_at(obj_dude, v5); } break; } } return; } if (gmouse_3d_current_mode == GAME_MOUSE_MODE_CROSSHAIR) { Object* v7 = object_under_mouse(OBJ_TYPE_CRITTER, false, map_elevation); if (v7 == NULL) { v7 = object_under_mouse(-1, false, map_elevation); if (!gmObjIsValidTarget(v7)) { v7 = NULL; } } if (v7 != NULL) { combat_attack_this(v7); gmouse_3d_hover_test = true; gmouse_3d_last_mouse_y = mouseY; gmouse_3d_last_mouse_x = mouseX; gmouse_3d_last_move_time = get_time() - 250; } return; } if (gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_CROSSHAIR) { Object* object = object_under_mouse(-1, true, map_elevation); if (object != NULL) { Object* weapon; if (intface_get_current_item(&weapon) != -1) { if (isInCombat()) { int hitMode = intface_is_item_right_hand() ? HIT_MODE_RIGHT_WEAPON_PRIMARY : HIT_MODE_LEFT_WEAPON_PRIMARY; int actionPointsRequired = item_mp_cost(obj_dude, hitMode, false); if (actionPointsRequired <= obj_dude->data.critter.combat.ap) { if (action_use_an_item_on_object(obj_dude, object, weapon) != -1) { int actionPoints = obj_dude->data.critter.combat.ap; if (actionPointsRequired > actionPoints) { obj_dude->data.critter.combat.ap = 0; } else { obj_dude->data.critter.combat.ap -= actionPointsRequired; } intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } } } else { action_use_an_item_on_object(obj_dude, object, weapon); } } } gmouse_set_cursor(MOUSE_CURSOR_NONE); gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); return; } if (gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_FIRST_AID || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_DOCTOR || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_LOCKPICK || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_STEAL || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_TRAPS || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_SCIENCE || gmouse_3d_current_mode == GAME_MOUSE_MODE_USE_REPAIR) { Object* object = object_under_mouse(-1, 1, map_elevation); if (object == NULL || action_use_skill_on(obj_dude, object, gmouse_skill_table[gmouse_3d_current_mode - FIRST_GAME_MOUSE_MODE_SKILL]) != -1) { gmouse_set_cursor(MOUSE_CURSOR_NONE); gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); } return; } } if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) == MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT && gmouse_3d_current_mode == GAME_MOUSE_MODE_ARROW) { Object* v16 = object_under_mouse(-1, true, map_elevation); if (v16 != NULL) { int actionMenuItemsCount = 0; int actionMenuItems[6]; switch (FID_TYPE(v16->fid)) { case OBJ_TYPE_ITEM: actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; if (item_get_type(v16) == ITEM_TYPE_CONTAINER) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL; } actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL; break; case OBJ_TYPE_CRITTER: if (v16 == obj_dude) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_ROTATE; } else { if (obj_action_can_talk_to(v16)) { if (!isInCombat()) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_TALK; } } else { if (!critter_flag_check(v16->pid, CRITTER_NO_STEAL)) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE; } } if (action_can_be_pushed(obj_dude, v16)) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_PUSH; } } actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL; break; case OBJ_TYPE_SCENERY: if (obj_action_can_use(v16)) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE; } actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL; actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL; break; case OBJ_TYPE_WALL: actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_LOOK; if (obj_action_can_use(v16)) { actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY; } actionMenuItems[actionMenuItemsCount++] = GAME_MOUSE_ACTION_MENU_ITEM_CANCEL; break; } if (gmouse_3d_build_menu_frame(mouseX, mouseY, actionMenuItems, actionMenuItemsCount, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99) == 0) { Rect v43; int fid = art_id(OBJ_TYPE_INTERFACE, 283, 0, 0, 0); // NOTE: Uninline. if (gmouse_3d_set_flat_fid(fid, &v43) == 0 && gmouse_3d_move_to(mouseX, mouseY, map_elevation, &v43) == 0) { tile_refresh_rect(&v43, map_elevation); map_disable_bk_processes(); int v33 = mouseY; int actionIndex = 0; while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { get_input(); if (game_user_wants_to_quit != 0) { actionMenuItems[actionIndex] = 0; } int v48; int v47; mouse_get_position(&v48, &v47); if (abs(v47 - v33) > 10) { if (v33 >= v47) { actionIndex -= 1; } else { actionIndex += 1; } if (gmouse_3d_highlight_menu_frame(actionIndex) == 0) { tile_refresh_rect(&v43, map_elevation); } v33 = v47; } } map_enable_bk_processes(); gmouse_3d_hover_test = false; gmouse_3d_last_mouse_x = mouseX; gmouse_3d_last_mouse_y = mouseY; gmouse_3d_last_move_time = get_time(); mouse_set_position(mouseX, v33); if (gmouse_3d_reset_flat_fid(&v43) == 0) { tile_refresh_rect(&v43, map_elevation); } switch (actionMenuItems[actionIndex]) { case GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY: use_inventory_on(v16); break; case GAME_MOUSE_ACTION_MENU_ITEM_LOOK: if (obj_examine(obj_dude, v16) == -1) { obj_look_at(obj_dude, v16); } break; case GAME_MOUSE_ACTION_MENU_ITEM_ROTATE: if (obj_inc_rotation(v16, &v43) == 0) { tile_refresh_rect(&v43, v16->elevation); } break; case GAME_MOUSE_ACTION_MENU_ITEM_TALK: action_talk_to(obj_dude, v16); break; case GAME_MOUSE_ACTION_MENU_ITEM_USE: switch (FID_TYPE(v16->fid)) { case OBJ_TYPE_SCENERY: action_use_an_object(obj_dude, v16); break; case OBJ_TYPE_CRITTER: action_loot_container(obj_dude, v16); break; default: action_get_an_object(obj_dude, v16); break; } break; case GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL: if (1) { int skill = -1; int rc = skilldex_select(); switch (rc) { case SKILLDEX_RC_SNEAK: action_skill_use(SKILL_SNEAK); break; case SKILLDEX_RC_LOCKPICK: skill = SKILL_LOCKPICK; break; case SKILLDEX_RC_STEAL: skill = SKILL_STEAL; break; case SKILLDEX_RC_TRAPS: skill = SKILL_TRAPS; break; case SKILLDEX_RC_FIRST_AID: skill = SKILL_FIRST_AID; break; case SKILLDEX_RC_DOCTOR: skill = SKILL_DOCTOR; break; case SKILLDEX_RC_SCIENCE: skill = SKILL_SCIENCE; break; case SKILLDEX_RC_REPAIR: skill = SKILL_REPAIR; break; } if (skill != -1) { action_use_skill_on(obj_dude, v16, skill); } } break; case GAME_MOUSE_ACTION_MENU_ITEM_PUSH: action_push_critter(obj_dude, v16); break; } } } } } } // 0x44C840 int gmouse_set_cursor(int cursor) { if (!gmouse_initialized) { return -1; } if (cursor != MOUSE_CURSOR_ARROW && cursor == gmouse_current_cursor && (gmouse_current_cursor < 25 || gmouse_current_cursor >= 27)) { return -1; } CacheEntry* mouseCursorFrmHandle; int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_cursor_nums[cursor], 0, 0, 0); Art* mouseCursorFrm = art_ptr_lock(fid, &mouseCursorFrmHandle); if (mouseCursorFrm == NULL) { return -1; } bool shouldUpdate = true; int frame = 0; if (cursor >= FIRST_GAME_MOUSE_ANIMATED_CURSOR) { unsigned int tick = get_time(); if ((obj_mouse_flat->flags & OBJECT_HIDDEN) == 0) { gmouse_3d_off(); } unsigned int delay = 1000 / art_frame_fps(mouseCursorFrm); if (elapsed_tocks(tick, gmouse_wait_cursor_time) < delay) { shouldUpdate = false; } else { if (art_frame_max_frame(mouseCursorFrm) <= gmouse_wait_cursor_frame) { gmouse_wait_cursor_frame = 0; } frame = gmouse_wait_cursor_frame; gmouse_wait_cursor_time = tick; gmouse_wait_cursor_frame++; } } if (!shouldUpdate) { return -1; } int width = art_frame_width(mouseCursorFrm, frame, 0); int height = art_frame_length(mouseCursorFrm, frame, 0); int offsetX; int offsetY; art_frame_offset(mouseCursorFrm, 0, &offsetX, &offsetY); offsetX = width / 2 - offsetX; offsetY = height - 1 - offsetY; unsigned char* mouseCursorFrmData = art_frame_data(mouseCursorFrm, frame, 0); if (mouse_set_shape(mouseCursorFrmData, width, height, width, offsetX, offsetY, 0) != 0) { return -1; } if (gmouse_current_cursor_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_current_cursor_key); } gmouse_current_cursor = cursor; gmouse_current_cursor_key = mouseCursorFrmHandle; return 0; } // 0x44C9E8 int gmouse_get_cursor() { return gmouse_current_cursor; } // NOTE: Unused. // // 0x44C9F0 void gmouse_set_mapper_mode(int mode) { gmouse_mapper_mode = mode; } // 0x44C9F8 void gmouse_3d_enable_modes() { gmouse_3d_modes_enabled = 1; } // NOTE: Unused. // // 0x44CA04 void gmouse_3d_disable_modes() { gmouse_3d_modes_enabled = 0; } // NOTE: Unused. // // 0x44CA10 int gmouse_3d_modes_are_enabled() { return gmouse_3d_modes_enabled; } // 0x44CA18 void gmouse_3d_set_mode(int mode) { if (!gmouse_initialized) { return; } if (!gmouse_3d_modes_enabled) { return; } if (mode == gmouse_3d_current_mode) { return; } int fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0); gmouse_3d_set_fid(fid); fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[mode], 0, 0, 0); Rect rect; // NOTE: Uninline. if (gmouse_3d_set_flat_fid(fid, &rect) == -1) { return; } int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); Rect r2; if (gmouse_3d_move_to(mouseX, mouseY, map_elevation, &r2) == 0) { rect_min_bound(&rect, &r2, &rect); } int v5 = 0; if (gmouse_3d_current_mode == GAME_MOUSE_MODE_CROSSHAIR) { v5 = -1; } if (mode != 0) { if (mode == GAME_MOUSE_MODE_CROSSHAIR) { v5 = 1; } if (gmouse_3d_current_mode == 0) { if (obj_turn_off_outline(obj_mouse_flat, &r2) == 0) { rect_min_bound(&rect, &r2, &rect); } } } else { if (obj_turn_on_outline(obj_mouse_flat, &r2) == 0) { rect_min_bound(&rect, &r2, &rect); } } gmouse_3d_current_mode = mode; gmouse_3d_hover_test = false; gmouse_3d_last_move_time = get_time(); tile_refresh_rect(&rect, map_elevation); switch (v5) { case 1: combat_outline_on(); break; case -1: combat_outline_off(); break; } } // 0x44CB6C int gmouse_3d_get_mode() { return gmouse_3d_current_mode; } // 0x44CB74 void gmouse_3d_toggle_mode() { int mode = (gmouse_3d_current_mode + 1) % 3; if (isInCombat()) { Object* item; if (intface_get_current_item(&item) == 0) { if (item != NULL && item_get_type(item) != ITEM_TYPE_WEAPON && mode == GAME_MOUSE_MODE_CROSSHAIR) { mode = GAME_MOUSE_MODE_MOVE; } } } else { if (mode == GAME_MOUSE_MODE_CROSSHAIR) { mode = GAME_MOUSE_MODE_MOVE; } } gmouse_3d_set_mode(mode); } // 0x44CBD0 void gmouse_3d_refresh() { gmouse_3d_last_mouse_x = -1; gmouse_3d_last_mouse_y = -1; gmouse_3d_hover_test = false; gmouse_3d_last_move_time = 0; gmouse_bk_process(); } // 0x44CBFC int gmouse_3d_set_fid(int fid) { if (!gmouse_initialized) { return -1; } if (!art_exists(fid)) { return -1; } if (obj_mouse->fid == fid) { return -1; } if (!gmouse_mapper_mode) { return obj_change_fid(obj_mouse, fid, NULL); } int v1 = 0; Rect oldRect; if (obj_mouse->fid != -1) { obj_bound(obj_mouse, &oldRect); v1 |= 1; } int rc = -1; Rect rect; if (obj_change_fid(obj_mouse, fid, &rect) == 0) { rc = 0; v1 |= 2; } if ((obj_mouse_flat->flags & OBJECT_HIDDEN) == 0) { if (v1 == 1) { tile_refresh_rect(&oldRect, map_elevation); } else if (v1 == 2) { tile_refresh_rect(&rect, map_elevation); } else if (v1 == 3) { rect_min_bound(&oldRect, &rect, &oldRect); tile_refresh_rect(&oldRect, map_elevation); } } return rc; } // NOTE: Unused. // // 0x44CCF4 int gmouse_3d_get_fid() { if (gmouse_initialized) { return obj_mouse->fid; } return -1; } // 0x44CD0C void gmouse_3d_reset_fid() { int fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0); gmouse_3d_set_fid(fid); } // 0x44CD2C void gmouse_3d_on() { if (!gmouse_initialized) { return; } int v2 = 0; Rect rect1; if (obj_turn_on(obj_mouse, &rect1) == 0) { v2 |= 1; } Rect rect2; if (obj_turn_on(obj_mouse_flat, &rect2) == 0) { v2 |= 2; } Rect tmp; if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) { if (obj_turn_off_outline(obj_mouse_flat, &tmp) == 0) { if ((v2 & 2) != 0) { rect_min_bound(&rect2, &tmp, &rect2); } else { memcpy(&rect2, &tmp, sizeof(rect2)); v2 |= 2; } } } if (gmouse_3d_reset_flat_fid(&tmp) == 0) { if ((v2 & 2) != 0) { rect_min_bound(&rect2, &tmp, &rect2); } else { memcpy(&rect2, &tmp, sizeof(rect2)); v2 |= 2; } } if (v2 != 0) { Rect* rect; switch (v2) { case 1: rect = &rect1; break; case 2: rect = &rect2; break; case 3: rect_min_bound(&rect1, &rect2, &rect1); rect = &rect1; break; default: assert(false && "Should be unreachable"); } tile_refresh_rect(rect, map_elevation); } gmouse_3d_hover_test = false; gmouse_3d_last_move_time = get_time() - 250; } // 0x44CE34 void gmouse_3d_off() { if (!gmouse_initialized) { return; } int v1 = 0; Rect rect1; if (obj_turn_off(obj_mouse, &rect1) == 0) { v1 |= 1; } Rect rect2; if (obj_turn_off(obj_mouse_flat, &rect2) == 0) { v1 |= 2; } if (v1 == 1) { tile_refresh_rect(&rect1, map_elevation); } else if (v1 == 2) { tile_refresh_rect(&rect2, map_elevation); } else if (v1 == 3) { rect_min_bound(&rect1, &rect2, &rect1); tile_refresh_rect(&rect1, map_elevation); } } // 0x44CEB0 bool gmouse_3d_is_on() { return (obj_mouse_flat->flags & OBJECT_HIDDEN) == 0; } // 0x44CEC4 Object* object_under_mouse(int objectType, bool a2, int elevation) { int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); bool v13 = false; if (objectType == -1) { if (square_roof_intersect(mouseX, mouseY, elevation)) { if (obj_intersects_with(obj_egg, mouseX, mouseY) == 0) { v13 = true; } } } Object* v4 = NULL; if (!v13) { ObjectWithFlags* entries; int count = obj_create_intersect_list(mouseX, mouseY, elevation, objectType, &entries); for (int index = count - 1; index >= 0; index--) { ObjectWithFlags* ptr = &(entries[index]); if (a2 || obj_dude != ptr->object) { v4 = ptr->object; if ((ptr->flags & 0x01) != 0) { if ((ptr->flags & 0x04) == 0) { if (FID_TYPE(ptr->object->fid) != OBJ_TYPE_CRITTER || (ptr->object->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_DEAD)) == 0) { break; } } } } } if (count != 0) { obj_delete_intersect_list(&entries); } } return v4; } // 0x44CFA0 int gmouse_3d_build_pick_frame(int x, int y, int menuItem, int width, int height) { CacheEntry* menuItemFrmHandle; int menuItemFid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_action_nums[menuItem], 0, 0, 0); Art* menuItemFrm = art_ptr_lock(menuItemFid, &menuItemFrmHandle); if (menuItemFrm == NULL) { return -1; } CacheEntry* arrowFrmHandle; int arrowFid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[GAME_MOUSE_MODE_ARROW], 0, 0, 0); Art* arrowFrm = art_ptr_lock(arrowFid, &arrowFrmHandle); if (arrowFrm == NULL) { art_ptr_unlock(menuItemFrmHandle); // FIXME: Why this is success? return 0; } unsigned char* arrowFrmData = art_frame_data(arrowFrm, 0, 0); int arrowFrmWidth = art_frame_width(arrowFrm, 0, 0); int arrowFrmHeight = art_frame_length(arrowFrm, 0, 0); unsigned char* menuItemFrmData = art_frame_data(menuItemFrm, 0, 0); int menuItemFrmWidth = art_frame_width(menuItemFrm, 0, 0); int menuItemFrmHeight = art_frame_length(menuItemFrm, 0, 0); unsigned char* arrowFrmDest = gmouse_3d_pick_frame_data; unsigned char* menuItemFrmDest = gmouse_3d_pick_frame_data; gmouse_3d_pick_frame_hot_x = 0; gmouse_3d_pick_frame_hot_y = 0; gmouse_3d_pick_frame->xOffsets[0] = gmouse_3d_pick_frame_width / 2; gmouse_3d_pick_frame->yOffsets[0] = gmouse_3d_pick_frame_height - 1; int maxX = x + menuItemFrmWidth + arrowFrmWidth - 1; int maxY = y + menuItemFrmHeight - 1; int shiftY = maxY - height + 2; if (maxX < width) { menuItemFrmDest += arrowFrmWidth; if (maxY >= height) { gmouse_3d_pick_frame_hot_y = shiftY; gmouse_3d_pick_frame->yOffsets[0] -= shiftY; arrowFrmDest += gmouse_3d_pick_frame_width * shiftY; } } else { art_ptr_unlock(arrowFrmHandle); arrowFid = art_id(OBJ_TYPE_INTERFACE, 285, 0, 0, 0); arrowFrm = art_ptr_lock(arrowFid, &arrowFrmHandle); arrowFrmData = art_frame_data(arrowFrm, 0, 0); arrowFrmDest += menuItemFrmWidth; gmouse_3d_pick_frame->xOffsets[0] = -gmouse_3d_pick_frame->xOffsets[0]; gmouse_3d_pick_frame_hot_x += menuItemFrmWidth + arrowFrmWidth; if (maxY >= height) { gmouse_3d_pick_frame_hot_y += shiftY; gmouse_3d_pick_frame->yOffsets[0] -= shiftY; arrowFrmDest += gmouse_3d_pick_frame_width * shiftY; } } memset(gmouse_3d_pick_frame_data, 0, gmouse_3d_pick_frame_size); buf_to_buf(arrowFrmData, arrowFrmWidth, arrowFrmHeight, arrowFrmWidth, arrowFrmDest, gmouse_3d_pick_frame_width); buf_to_buf(menuItemFrmData, menuItemFrmWidth, menuItemFrmHeight, menuItemFrmWidth, menuItemFrmDest, gmouse_3d_pick_frame_width); art_ptr_unlock(arrowFrmHandle); art_ptr_unlock(menuItemFrmHandle); return 0; } // 0x44D200 int gmouse_3d_pick_frame_hot(int* a1, int* a2) { *a1 = gmouse_3d_pick_frame_hot_x; *a2 = gmouse_3d_pick_frame_hot_y; return 0; } // 0x44D214 int gmouse_3d_build_menu_frame(int x, int y, const int* menuItems, int menuItemsLength, int width, int height) { gmouse_3d_menu_actions_start = NULL; gmouse_3d_menu_current_action_index = 0; gmouse_3d_menu_available_actions = 0; if (menuItems == NULL) { return -1; } if (menuItemsLength == 0 || menuItemsLength >= GAME_MOUSE_ACTION_MENU_ITEM_COUNT) { return -1; } CacheEntry* menuItemFrmHandles[GAME_MOUSE_ACTION_MENU_ITEM_COUNT]; Art* menuItemFrms[GAME_MOUSE_ACTION_MENU_ITEM_COUNT]; for (int index = 0; index < menuItemsLength; index++) { int frmId = gmouse_3d_action_nums[menuItems[index]] & 0xFFFF; if (index == 0) { frmId -= 1; } int fid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0); menuItemFrms[index] = art_ptr_lock(fid, &(menuItemFrmHandles[index])); if (menuItemFrms[index] == NULL) { while (--index >= 0) { art_ptr_unlock(menuItemFrmHandles[index]); } return -1; } } int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[GAME_MOUSE_MODE_ARROW], 0, 0, 0); CacheEntry* arrowFrmHandle; Art* arrowFrm = art_ptr_lock(fid, &arrowFrmHandle); if (arrowFrm == NULL) { // FIXME: Unlock arts. return -1; } int arrowWidth = art_frame_width(arrowFrm, 0, 0); int arrowHeight = art_frame_length(arrowFrm, 0, 0); int menuItemWidth = art_frame_width(menuItemFrms[0], 0, 0); int menuItemHeight = art_frame_length(menuItemFrms[0], 0, 0); gmouse_3d_menu_frame_hot_x = 0; gmouse_3d_menu_frame_hot_y = 0; gmouse_3d_menu_frame->xOffsets[0] = gmouse_3d_menu_frame_width / 2; gmouse_3d_menu_frame->yOffsets[0] = gmouse_3d_menu_frame_height - 1; int v60 = y + menuItemsLength * menuItemHeight - 1; int v24 = v60 - height + 2; unsigned char* v22 = gmouse_3d_menu_frame_data; unsigned char* v58 = v22; unsigned char* arrowData; if (x + arrowWidth + menuItemWidth - 1 < width) { arrowData = art_frame_data(arrowFrm, 0, 0); v58 = v22 + arrowWidth; if (height <= v60) { gmouse_3d_menu_frame_hot_y += v24; v22 += gmouse_3d_menu_frame_width * v24; gmouse_3d_menu_frame->yOffsets[0] -= v24; } } else { // Mirrored arrow (from left to right). fid = art_id(OBJ_TYPE_INTERFACE, 285, 0, 0, 0); arrowFrm = art_ptr_lock(fid, &arrowFrmHandle); arrowData = art_frame_data(arrowFrm, 0, 0); gmouse_3d_menu_frame->xOffsets[0] = -gmouse_3d_menu_frame->xOffsets[0]; gmouse_3d_menu_frame_hot_x += menuItemWidth + arrowWidth; if (v60 >= height) { gmouse_3d_menu_frame_hot_y += v24; gmouse_3d_menu_frame->yOffsets[0] -= v24; v22 += gmouse_3d_menu_frame_width * v24; } } memset(gmouse_3d_menu_frame_data, 0, gmouse_3d_menu_frame_size); buf_to_buf(arrowData, arrowWidth, arrowHeight, arrowWidth, v22, gmouse_3d_pick_frame_width); unsigned char* v38 = v58; for (int index = 0; index < menuItemsLength; index++) { unsigned char* data = art_frame_data(menuItemFrms[index], 0, 0); buf_to_buf(data, menuItemWidth, menuItemHeight, menuItemWidth, v38, gmouse_3d_pick_frame_width); v38 += gmouse_3d_menu_frame_width * menuItemHeight; } art_ptr_unlock(arrowFrmHandle); for (int index = 0; index < menuItemsLength; index++) { art_ptr_unlock(menuItemFrmHandles[index]); } memcpy(gmouse_3d_menu_frame_actions, menuItems, sizeof(*gmouse_3d_menu_frame_actions) * menuItemsLength); gmouse_3d_menu_available_actions = menuItemsLength; gmouse_3d_menu_actions_start = v58; Sound* sound = gsound_load_sound("iaccuxx1", NULL); if (sound != NULL) { gsound_play_sound(sound); } return 0; } // NOTE: Unused. // // 0x44D61C int gmouse_3d_menu_frame_hot(int* x, int* y) { *x = gmouse_3d_menu_frame_hot_x; *y = gmouse_3d_menu_frame_hot_y; return 0; } // 0x44D630 int gmouse_3d_highlight_menu_frame(int menuItemIndex) { if (menuItemIndex < 0 || menuItemIndex >= gmouse_3d_menu_available_actions) { return -1; } CacheEntry* handle; int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_action_nums[gmouse_3d_menu_frame_actions[gmouse_3d_menu_current_action_index]], 0, 0, 0); Art* art = art_ptr_lock(fid, &handle); if (art == NULL) { return -1; } int width = art_frame_width(art, 0, 0); int height = art_frame_length(art, 0, 0); unsigned char* data = art_frame_data(art, 0, 0); buf_to_buf(data, width, height, width, gmouse_3d_menu_actions_start + gmouse_3d_menu_frame_width * height * gmouse_3d_menu_current_action_index, gmouse_3d_menu_frame_width); art_ptr_unlock(handle); fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_action_nums[gmouse_3d_menu_frame_actions[menuItemIndex]] - 1, 0, 0, 0); art = art_ptr_lock(fid, &handle); if (art == NULL) { return -1; } data = art_frame_data(art, 0, 0); buf_to_buf(data, width, height, width, gmouse_3d_menu_actions_start + gmouse_3d_menu_frame_width * height * menuItemIndex, gmouse_3d_menu_frame_width); art_ptr_unlock(handle); gmouse_3d_menu_current_action_index = menuItemIndex; return 0; } // 0x44D774 int gmouse_3d_build_to_hit_frame(const char* string, int color) { CacheEntry* crosshairFrmHandle; int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[GAME_MOUSE_MODE_CROSSHAIR], 0, 0, 0); Art* crosshairFrm = art_ptr_lock(fid, &crosshairFrmHandle); if (crosshairFrm == NULL) { return -1; } memset(gmouse_3d_to_hit_frame_data, 0, gmouse_3d_to_hit_frame_size); int crosshairFrmWidth = art_frame_width(crosshairFrm, 0, 0); int crosshairFrmHeight = art_frame_length(crosshairFrm, 0, 0); unsigned char* crosshairFrmData = art_frame_data(crosshairFrm, 0, 0); buf_to_buf(crosshairFrmData, crosshairFrmWidth, crosshairFrmHeight, crosshairFrmWidth, gmouse_3d_to_hit_frame_data, gmouse_3d_to_hit_frame_width); int oldFont = text_curr(); text_font(101); text_to_buf(gmouse_3d_to_hit_frame_data + gmouse_3d_to_hit_frame_width + crosshairFrmWidth + 1, string, gmouse_3d_to_hit_frame_width - crosshairFrmWidth, gmouse_3d_to_hit_frame_width, color); buf_outline(gmouse_3d_to_hit_frame_data + crosshairFrmWidth, gmouse_3d_to_hit_frame_width - crosshairFrmWidth, gmouse_3d_to_hit_frame_height, gmouse_3d_to_hit_frame_width, colorTable[0]); text_font(oldFont); art_ptr_unlock(crosshairFrmHandle); return 0; } // 0x44D878 int gmouse_3d_build_hex_frame(const char* string, int color) { memset(gmouse_3d_hex_frame_data, 0, gmouse_3d_hex_frame_width * gmouse_3d_hex_frame_height); if (*string == '\0') { return 0; } int oldFont = text_curr(); text_font(101); int length = text_width(string); text_to_buf(gmouse_3d_hex_frame_data + gmouse_3d_hex_frame_width * (gmouse_3d_hex_frame_height - text_height()) / 2 + (gmouse_3d_hex_frame_width - length) / 2, string, gmouse_3d_hex_frame_width, gmouse_3d_hex_frame_width, color); buf_outline(gmouse_3d_hex_frame_data, gmouse_3d_hex_frame_width, gmouse_3d_hex_frame_height, gmouse_3d_hex_frame_width, colorTable[0]); text_font(oldFont); int fid = art_id(OBJ_TYPE_INTERFACE, 1, 0, 0, 0); gmouse_3d_set_fid(fid); return 0; } // 0x44D954 void gmouse_3d_synch_item_highlight() { bool itemHighlight; if (configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, &itemHighlight)) { gmouse_3d_item_highlight = itemHighlight; } } // 0x44D984 static int gmouse_3d_init() { int fid; if (gmouse_3d_initialized) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0); if (obj_new(&obj_mouse, fid, -1) != 0) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 1, 0, 0, 0); if (obj_new(&obj_mouse_flat, fid, -1) != 0) { return -1; } if (obj_outline_object(obj_mouse_flat, OUTLINE_PALETTED | OUTLINE_TYPE_2, NULL) != 0) { return -1; } if (gmouse_3d_lock_frames() != 0) { return -1; } obj_mouse->flags |= OBJECT_LIGHT_THRU; obj_mouse->flags |= OBJECT_TEMPORARY; obj_mouse->flags |= OBJECT_FLAG_0x400; obj_mouse->flags |= OBJECT_SHOOT_THRU; obj_mouse->flags |= OBJECT_NO_BLOCK; obj_mouse_flat->flags |= OBJECT_FLAG_0x400; obj_mouse_flat->flags |= OBJECT_TEMPORARY; obj_mouse_flat->flags |= OBJECT_LIGHT_THRU; obj_mouse_flat->flags |= OBJECT_SHOOT_THRU; obj_mouse_flat->flags |= OBJECT_NO_BLOCK; obj_toggle_flat(obj_mouse_flat, NULL); int x; int y; mouse_get_position(&x, &y); Rect v9; gmouse_3d_move_to(x, y, map_elevation, &v9); gmouse_3d_initialized = true; gmouse_3d_synch_item_highlight(); return 0; } // NOTE: Inlined. // // 0x44DAC0 static int gmouse_3d_reset() { if (!gmouse_3d_initialized) { return -1; } // NOTE: Uninline. gmouse_3d_enable_modes(); // NOTE: Uninline. gmouse_3d_reset_fid(); gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); gmouse_3d_on(); gmouse_3d_last_mouse_x = -1; gmouse_3d_last_mouse_y = -1; gmouse_3d_hover_test = false; gmouse_3d_last_move_time = get_time(); gmouse_3d_synch_item_highlight(); return 0; } // NOTE: Inlined. // // 0x44DB34 static void gmouse_3d_exit() { if (gmouse_3d_initialized) { gmouse_3d_unlock_frames(); obj_mouse->flags &= ~OBJECT_TEMPORARY; obj_mouse_flat->flags &= ~OBJECT_TEMPORARY; obj_erase_object(obj_mouse, NULL); obj_erase_object(obj_mouse_flat, NULL); gmouse_3d_initialized = false; } } // 0x44DB78 static int gmouse_3d_lock_frames() { int fid; // actmenu.frm - action menu fid = art_id(OBJ_TYPE_INTERFACE, 283, 0, 0, 0); gmouse_3d_menu_frame = art_ptr_lock(fid, &gmouse_3d_menu_frame_key); if (gmouse_3d_menu_frame == NULL) { goto err; } // actpick.frm - action pick fid = art_id(OBJ_TYPE_INTERFACE, 282, 0, 0, 0); gmouse_3d_pick_frame = art_ptr_lock(fid, &gmouse_3d_pick_frame_key); if (gmouse_3d_pick_frame == NULL) { goto err; } // acttohit.frm - action to hit fid = art_id(OBJ_TYPE_INTERFACE, 284, 0, 0, 0); gmouse_3d_to_hit_frame = art_ptr_lock(fid, &gmouse_3d_to_hit_frame_key); if (gmouse_3d_to_hit_frame == NULL) { goto err; } // blank.frm - used be mset000.frm for top of bouncing mouse cursor fid = art_id(OBJ_TYPE_INTERFACE, 0, 0, 0, 0); gmouse_3d_hex_base_frame = art_ptr_lock(fid, &gmouse_3d_hex_base_frame_key); if (gmouse_3d_hex_base_frame == NULL) { goto err; } // msef000.frm - hex mouse cursor fid = art_id(OBJ_TYPE_INTERFACE, 1, 0, 0, 0); gmouse_3d_hex_frame = art_ptr_lock(fid, &gmouse_3d_hex_frame_key); if (gmouse_3d_hex_frame == NULL) { goto err; } gmouse_3d_menu_frame_width = art_frame_width(gmouse_3d_menu_frame, 0, 0); gmouse_3d_menu_frame_height = art_frame_length(gmouse_3d_menu_frame, 0, 0); gmouse_3d_menu_frame_size = gmouse_3d_menu_frame_width * gmouse_3d_menu_frame_height; gmouse_3d_menu_frame_data = art_frame_data(gmouse_3d_menu_frame, 0, 0); gmouse_3d_pick_frame_width = art_frame_width(gmouse_3d_pick_frame, 0, 0); gmouse_3d_pick_frame_height = art_frame_length(gmouse_3d_pick_frame, 0, 0); gmouse_3d_pick_frame_size = gmouse_3d_pick_frame_width * gmouse_3d_pick_frame_height; gmouse_3d_pick_frame_data = art_frame_data(gmouse_3d_pick_frame, 0, 0); gmouse_3d_to_hit_frame_width = art_frame_width(gmouse_3d_to_hit_frame, 0, 0); gmouse_3d_to_hit_frame_height = art_frame_length(gmouse_3d_to_hit_frame, 0, 0); gmouse_3d_to_hit_frame_size = gmouse_3d_to_hit_frame_width * gmouse_3d_to_hit_frame_height; gmouse_3d_to_hit_frame_data = art_frame_data(gmouse_3d_to_hit_frame, 0, 0); gmouse_3d_hex_base_frame_width = art_frame_width(gmouse_3d_hex_base_frame, 0, 0); gmouse_3d_hex_base_frame_height = art_frame_length(gmouse_3d_hex_base_frame, 0, 0); gmouse_3d_hex_base_frame_size = gmouse_3d_hex_base_frame_width * gmouse_3d_hex_base_frame_height; gmouse_3d_hex_base_frame_data = art_frame_data(gmouse_3d_hex_base_frame, 0, 0); gmouse_3d_hex_frame_width = art_frame_width(gmouse_3d_hex_frame, 0, 0); gmouse_3d_hex_frame_height = art_frame_length(gmouse_3d_hex_frame, 0, 0); gmouse_3d_hex_frame_size = gmouse_3d_hex_frame_width * gmouse_3d_hex_frame_height; gmouse_3d_hex_frame_data = art_frame_data(gmouse_3d_hex_frame, 0, 0); return 0; err: // NOTE: Original code is different. There is no call to this function. // Instead it either use deep nesting or bunch of goto's to unwind // locked frms from the point of failure. gmouse_3d_unlock_frames(); return -1; } // 0x44DE44 static void gmouse_3d_unlock_frames() { if (gmouse_3d_hex_base_frame_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_3d_hex_base_frame_key); } gmouse_3d_hex_base_frame = NULL; gmouse_3d_hex_base_frame_key = INVALID_CACHE_ENTRY; if (gmouse_3d_hex_frame_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_3d_hex_frame_key); } gmouse_3d_hex_frame = NULL; gmouse_3d_hex_frame_key = INVALID_CACHE_ENTRY; if (gmouse_3d_to_hit_frame_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_3d_to_hit_frame_key); } gmouse_3d_to_hit_frame = NULL; gmouse_3d_to_hit_frame_key = INVALID_CACHE_ENTRY; if (gmouse_3d_menu_frame_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_3d_menu_frame_key); } gmouse_3d_menu_frame = NULL; gmouse_3d_menu_frame_key = INVALID_CACHE_ENTRY; if (gmouse_3d_pick_frame_key != INVALID_CACHE_ENTRY) { art_ptr_unlock(gmouse_3d_pick_frame_key); } gmouse_3d_pick_frame = NULL; gmouse_3d_pick_frame_key = INVALID_CACHE_ENTRY; gmouse_3d_pick_frame_data = NULL; gmouse_3d_pick_frame_width = 0; gmouse_3d_pick_frame_height = 0; gmouse_3d_pick_frame_size = 0; } // NOTE: Inlined. // // 0x44DF1C static int gmouse_3d_set_flat_fid(int fid, Rect* rect) { if (obj_change_fid(obj_mouse_flat, fid, rect) == 0) { return 0; } return -1; } // 0x44DF40 static int gmouse_3d_reset_flat_fid(Rect* rect) { int fid = art_id(OBJ_TYPE_INTERFACE, gmouse_3d_mode_nums[gmouse_3d_current_mode], 0, 0, 0); if (obj_mouse_flat->fid == fid) { return -1; } // NOTE: Uninline. return gmouse_3d_set_flat_fid(fid, rect); } // 0x44DF94 static int gmouse_3d_move_to(int x, int y, int elevation, Rect* a4) { if (gmouse_mapper_mode == 0) { if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) { int offsetX = 0; int offsetY = 0; CacheEntry* hexCursorFrmHandle; Art* hexCursorFrm = art_ptr_lock(obj_mouse_flat->fid, &hexCursorFrmHandle); if (hexCursorFrm != NULL) { art_frame_offset(hexCursorFrm, 0, &offsetX, &offsetY); int frameOffsetX; int frameOffsetY; art_frame_hot(hexCursorFrm, 0, 0, &frameOffsetX, &frameOffsetY); offsetX += frameOffsetX; offsetY += frameOffsetY; art_ptr_unlock(hexCursorFrmHandle); } obj_move(obj_mouse_flat, x + offsetX, y + offsetY, elevation, a4); } else { int tile = tile_num(x, y, 0); if (tile != -1) { int screenX; int screenY; bool v1 = false; Rect rect1; if (tile_coord(tile, &screenX, &screenY, 0) == 0) { if (obj_move(obj_mouse, screenX + 16, screenY + 15, 0, &rect1) == 0) { v1 = true; } } Rect rect2; if (obj_move_to_tile(obj_mouse_flat, tile, elevation, &rect2) == 0) { if (v1) { rect_min_bound(&rect1, &rect2, &rect1); } else { rectCopy(&rect1, &rect2); } rectCopy(a4, &rect1); } } } return 0; } int tile; int x1 = 0; int y1 = 0; int fid = obj_mouse->fid; if (FID_TYPE(fid) == OBJ_TYPE_TILE) { int squareTile = square_num(x, y, elevation); if (squareTile == -1) { tile = HEX_GRID_WIDTH * (2 * (squareTile / SQUARE_GRID_WIDTH) + 1) + 2 * (squareTile % SQUARE_GRID_WIDTH) + 1; x1 = -8; y1 = 13; char* executable; config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, &executable); if (stricmp(executable, "mapper") == 0) { if (tile_roof_visible()) { if ((obj_dude->flags & OBJECT_HIDDEN) == 0) { y1 = -83; } } } } else { tile = -1; } } else { tile = tile_num(x, y, elevation); } if (tile != -1) { bool v1 = false; Rect rect1; Rect rect2; if (obj_move_to_tile(obj_mouse, tile, elevation, &rect1) == 0) { if (x1 != 0 || y1 != 0) { if (obj_offset(obj_mouse, x1, y1, &rect2) == 0) { rect_min_bound(&rect1, &rect2, &rect1); } } v1 = true; } if (gmouse_3d_current_mode != GAME_MOUSE_MODE_MOVE) { int offsetX = 0; int offsetY = 0; CacheEntry* hexCursorFrmHandle; Art* hexCursorFrm = art_ptr_lock(obj_mouse_flat->fid, &hexCursorFrmHandle); if (hexCursorFrm != NULL) { art_frame_offset(hexCursorFrm, 0, &offsetX, &offsetY); int frameOffsetX; int frameOffsetY; art_frame_hot(hexCursorFrm, 0, 0, &frameOffsetX, &frameOffsetY); offsetX += frameOffsetX; offsetY += frameOffsetY; art_ptr_unlock(hexCursorFrmHandle); } if (obj_move(obj_mouse_flat, x + offsetX, y + offsetY, elevation, &rect2) == 0) { if (v1) { rect_min_bound(&rect1, &rect2, &rect1); } else { rectCopy(&rect1, &rect2); v1 = true; } } } else { if (obj_move_to_tile(obj_mouse_flat, tile, elevation, &rect2) == 0) { if (v1) { rect_min_bound(&rect1, &rect2, &rect1); } else { rectCopy(&rect1, &rect2); v1 = true; } } } if (v1) { rectCopy(a4, &rect1); } } return 0; } // 0x44E42C static int gmouse_check_scrolling(int x, int y, int cursor) { if (!gmouse_scrolling_enabled) { return -1; } int flags = 0; if (x <= scr_size.ulx) { flags |= SCROLLABLE_W; } if (x >= scr_size.lrx) { flags |= SCROLLABLE_E; } if (y <= scr_size.uly) { flags |= SCROLLABLE_N; } if (y >= scr_size.lry) { flags |= SCROLLABLE_S; } int dx = 0; int dy = 0; switch (flags) { case SCROLLABLE_W: dx = -1; cursor = MOUSE_CURSOR_SCROLL_W; break; case SCROLLABLE_E: dx = 1; cursor = MOUSE_CURSOR_SCROLL_E; break; case SCROLLABLE_N: dy = -1; cursor = MOUSE_CURSOR_SCROLL_N; break; case SCROLLABLE_N | SCROLLABLE_W: dx = -1; dy = -1; cursor = MOUSE_CURSOR_SCROLL_NW; break; case SCROLLABLE_N | SCROLLABLE_E: dx = 1; dy = -1; cursor = MOUSE_CURSOR_SCROLL_NE; break; case SCROLLABLE_S: dy = 1; cursor = MOUSE_CURSOR_SCROLL_S; break; case SCROLLABLE_S | SCROLLABLE_W: dx = -1; dy = 1; cursor = MOUSE_CURSOR_SCROLL_SW; break; case SCROLLABLE_S | SCROLLABLE_E: dx = 1; dy = 1; cursor = MOUSE_CURSOR_SCROLL_SE; break; } if (dx == 0 && dy == 0) { return -1; } int rc = map_scroll(dx, dy); switch (rc) { case -1: // Scrolling is blocked for whatever reason, upgrade cursor to // appropriate blocked version. cursor += 8; // FALLTHROUGH case 0: gmouse_set_cursor(cursor); break; } return 0; } // 0x44E544 void gmouse_remove_item_outline(Object* object) { if (outlined_object != NULL && outlined_object == object) { Rect rect; if (obj_remove_outline(object, &rect) == 0) { tile_refresh_rect(&rect, map_elevation); } outlined_object = NULL; } } // 0x44E580 static int gmObjIsValidTarget(Object* object) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_SCENERY) { return false; } Proto* proto; if (proto_ptr(object->pid, &proto) == -1) { return false; } return proto->scenery.type == SCENERY_TYPE_DOOR; } ================================================ FILE: src/game/gmouse.h ================================================ #ifndef FALLOUT_GAME_GMOUSE_H_ #define FALLOUT_GAME_GMOUSE_H_ #include #include "game/object_types.h" typedef enum GameMouseMode { GAME_MOUSE_MODE_MOVE, GAME_MOUSE_MODE_ARROW, GAME_MOUSE_MODE_CROSSHAIR, GAME_MOUSE_MODE_USE_CROSSHAIR, GAME_MOUSE_MODE_USE_FIRST_AID, GAME_MOUSE_MODE_USE_DOCTOR, GAME_MOUSE_MODE_USE_LOCKPICK, GAME_MOUSE_MODE_USE_STEAL, GAME_MOUSE_MODE_USE_TRAPS, GAME_MOUSE_MODE_USE_SCIENCE, GAME_MOUSE_MODE_USE_REPAIR, GAME_MOUSE_MODE_COUNT, FIRST_GAME_MOUSE_MODE_SKILL = GAME_MOUSE_MODE_USE_FIRST_AID, GAME_MOUSE_MODE_SKILL_COUNT = GAME_MOUSE_MODE_COUNT - FIRST_GAME_MOUSE_MODE_SKILL, } GameMouseMode; typedef enum GameMouseActionMenuItem { GAME_MOUSE_ACTION_MENU_ITEM_CANCEL = 0, GAME_MOUSE_ACTION_MENU_ITEM_DROP = 1, GAME_MOUSE_ACTION_MENU_ITEM_INVENTORY = 2, GAME_MOUSE_ACTION_MENU_ITEM_LOOK = 3, GAME_MOUSE_ACTION_MENU_ITEM_ROTATE = 4, GAME_MOUSE_ACTION_MENU_ITEM_TALK = 5, GAME_MOUSE_ACTION_MENU_ITEM_USE = 6, GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD = 7, GAME_MOUSE_ACTION_MENU_ITEM_USE_SKILL = 8, GAME_MOUSE_ACTION_MENU_ITEM_PUSH = 9, GAME_MOUSE_ACTION_MENU_ITEM_COUNT, } GameMouseActionMenuItem; typedef enum MouseCursorType { MOUSE_CURSOR_NONE, MOUSE_CURSOR_ARROW, MOUSE_CURSOR_SMALL_ARROW_UP, MOUSE_CURSOR_SMALL_ARROW_DOWN, MOUSE_CURSOR_SCROLL_NW, MOUSE_CURSOR_SCROLL_N, MOUSE_CURSOR_SCROLL_NE, MOUSE_CURSOR_SCROLL_E, MOUSE_CURSOR_SCROLL_SE, MOUSE_CURSOR_SCROLL_S, MOUSE_CURSOR_SCROLL_SW, MOUSE_CURSOR_SCROLL_W, MOUSE_CURSOR_SCROLL_NW_INVALID, MOUSE_CURSOR_SCROLL_N_INVALID, MOUSE_CURSOR_SCROLL_NE_INVALID, MOUSE_CURSOR_SCROLL_E_INVALID, MOUSE_CURSOR_SCROLL_SE_INVALID, MOUSE_CURSOR_SCROLL_S_INVALID, MOUSE_CURSOR_SCROLL_SW_INVALID, MOUSE_CURSOR_SCROLL_W_INVALID, MOUSE_CURSOR_CROSSHAIR, MOUSE_CURSOR_PLUS, MOUSE_CURSOR_DESTROY, MOUSE_CURSOR_USE_CROSSHAIR, MOUSE_CURSOR_WATCH, MOUSE_CURSOR_WAIT_PLANET, MOUSE_CURSOR_WAIT_WATCH, MOUSE_CURSOR_TYPE_COUNT, FIRST_GAME_MOUSE_ANIMATED_CURSOR = MOUSE_CURSOR_WAIT_PLANET, } MouseCursorType; extern bool gmouse_clicked_on_edge; extern Object* obj_mouse; extern Object* obj_mouse_flat; int gmouse_init(); int gmouse_reset(); void gmouse_exit(); void gmouse_enable(); void gmouse_disable(int a1); int gmouse_is_enabled(); void gmouse_enable_scrolling(); void gmouse_disable_scrolling(); int gmouse_scrolling_is_enabled(); void gmouse_set_click_to_scroll(int a1); int gmouse_get_click_to_scroll(); int gmouse_is_scrolling(); void gmouse_bk_process(); void gmouse_handle_event(int mouseX, int mouseY, int mouseState); int gmouse_set_cursor(int cursor); int gmouse_get_cursor(); void gmouse_set_mapper_mode(int mode); void gmouse_3d_enable_modes(); void gmouse_3d_disable_modes(); int gmouse_3d_modes_are_enabled(); void gmouse_3d_set_mode(int a1); int gmouse_3d_get_mode(); void gmouse_3d_toggle_mode(); void gmouse_3d_refresh(); int gmouse_3d_set_fid(int fid); int gmouse_3d_get_fid(); void gmouse_3d_reset_fid(); void gmouse_3d_on(); void gmouse_3d_off(); bool gmouse_3d_is_on(); Object* object_under_mouse(int objectType, bool a2, int elevation); int gmouse_3d_build_pick_frame(int x, int y, int menuItem, int width, int height); int gmouse_3d_pick_frame_hot(int* a1, int* a2); int gmouse_3d_build_menu_frame(int x, int y, const int* menuItems, int menuItemsCount, int width, int height); int gmouse_3d_menu_frame_hot(int* x, int* y); int gmouse_3d_highlight_menu_frame(int menuItemIndex); int gmouse_3d_build_to_hit_frame(const char* string, int color); int gmouse_3d_build_hex_frame(const char* string, int color); void gmouse_3d_synch_item_highlight(); void gmouse_remove_item_outline(Object* object); #endif /* FALLOUT_GAME_GMOUSE_H_ */ ================================================ FILE: src/game/gmovie.c ================================================ #include "game/gmovie.h" #include #include #define WIN32_LEAN_AND_MEAN #include #include "int/window.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gsound.h" #include "int/movie.h" #include "game/moviefx.h" #include "game/palette.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" #define GAME_MOVIE_WINDOW_WIDTH 640 #define GAME_MOVIE_WINDOW_HEIGHT 480 static char* gmovie_subtitle_func(char* movieFilePath); // 0x518DA0 static const char* movie_list[MOVIE_COUNT] = { "iplogo.mve", "intro.mve", "elder.mve", "vsuit.mve", "afailed.mve", "adestroy.mve", "car.mve", "cartucci.mve", "timeout.mve", "tanker.mve", "enclave.mve", "derrick.mve", "artimer1.mve", "artimer2.mve", "artimer3.mve", "artimer4.mve", "credits.mve", }; // 0x518DE4 static const char* subtitlePalList[MOVIE_COUNT] = { NULL, "art\\cuts\\introsub.pal", "art\\cuts\\eldersub.pal", NULL, "art\\cuts\\artmrsub.pal", NULL, NULL, NULL, "art\\cuts\\artmrsub.pal", NULL, NULL, NULL, "art\\cuts\\artmrsub.pal", "art\\cuts\\artmrsub.pal", "art\\cuts\\artmrsub.pal", "art\\cuts\\artmrsub.pal", "art\\cuts\\crdtssub.pal", }; // 0x518E28 static bool gmMovieIsPlaying = false; // 0x518E2C static bool gmPaletteWasFaded = false; // 0x596C78 static unsigned char gmovie_played_list[MOVIE_COUNT]; // gmovie_init // 0x44E5C0 int gmovie_init() { int v1 = 0; if (gsound_background_is_enabled()) { v1 = gsound_background_volume_get(); } movieSetVolume(v1); movieSetSubtitleFunc(gmovie_subtitle_func); memset(gmovie_played_list, 0, sizeof(gmovie_played_list)); gmMovieIsPlaying = false; gmPaletteWasFaded = false; return 0; } // 0x44E60C void gmovie_reset() { memset(gmovie_played_list, 0, sizeof(gmovie_played_list)); gmMovieIsPlaying = false; gmPaletteWasFaded = false; } // 0x44E638 int gmovie_load(File* stream) { if (db_fread(gmovie_played_list, sizeof(*gmovie_played_list), MOVIE_COUNT, stream) != MOVIE_COUNT) { return -1; } return 0; } // 0x44E664 int gmovie_save(File* stream) { if (db_fwrite(gmovie_played_list, sizeof(*gmovie_played_list), MOVIE_COUNT, stream) != MOVIE_COUNT) { return -1; } return 0; } // gmovie_play // 0x44E690 int gmovie_play(int movie, int flags) { gmMovieIsPlaying = true; const char* movieFileName = movie_list[movie]; debug_printf("\nPlaying movie: %s\n", movieFileName); char* language; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { debug_printf("\ngmovie_play() - Error: Unable to determine language!\n"); gmMovieIsPlaying = false; return -1; } char movieFilePath[MAX_PATH]; int movieFileSize; bool movieFound = false; if (stricmp(language, ENGLISH) != 0) { sprintf(movieFilePath, "art\\%s\\cuts\\%s", language, movie_list[movie]); movieFound = db_dir_entry(movieFilePath, &movieFileSize) == 0; } if (!movieFound) { sprintf(movieFilePath, "art\\cuts\\%s", movie_list[movie]); movieFound = db_dir_entry(movieFilePath, &movieFileSize) == 0; } if (!movieFound) { debug_printf("\ngmovie_play() - Error: Unable to open %s\n", movie_list[movie]); gmMovieIsPlaying = false; return -1; } if ((flags & GAME_MOVIE_FADE_IN) != 0) { palette_fade_to(black_palette); gmPaletteWasFaded = true; } int gameMovieWindowX = 0; int gameMovieWindowY = 0; int win = win_add(gameMovieWindowX, gameMovieWindowY, GAME_MOVIE_WINDOW_WIDTH, GAME_MOVIE_WINDOW_HEIGHT, 0, WINDOW_FLAG_0x10); if (win == -1) { gmMovieIsPlaying = false; return -1; } if ((flags & GAME_MOVIE_STOP_MUSIC) != 0) { gsound_background_stop(); } else if ((flags & GAME_MOVIE_PAUSE_MUSIC) != 0) { gsound_background_pause(); } win_draw(win); bool subtitlesEnabled = false; int v1 = 4; configGetBool(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitlesEnabled); if (subtitlesEnabled) { char* subtitlesFilePath = gmovie_subtitle_func(movieFilePath); int subtitlesFileSize; if (db_dir_entry(subtitlesFilePath, &subtitlesFileSize) == 0) { v1 = 12; } else { subtitlesEnabled = false; } } movieSetFlags(v1); int oldTextColor; int oldFont; if (subtitlesEnabled) { const char* subtitlesPaletteFilePath; if (subtitlePalList[movie] != NULL) { subtitlesPaletteFilePath = subtitlePalList[movie]; } else { subtitlesPaletteFilePath = "art\\cuts\\subtitle.pal"; } loadColorTable(subtitlesPaletteFilePath); oldTextColor = windowGetTextColor(); windowSetTextColor(1.0, 1.0, 1.0); oldFont = text_curr(); windowSetFont(101); } bool cursorWasHidden = mouse_hidden(); if (cursorWasHidden) { gmouse_set_cursor(MOUSE_CURSOR_NONE); mouse_show(); } while (mouse_get_buttons() != 0) { mouse_info(); } mouse_hide(); cycle_disable(); moviefx_start(movieFilePath); zero_vid_mem(); movieRun(win, movieFilePath); int v11 = 0; int buttons; do { if (!moviePlaying() || game_user_wants_to_quit || get_input() != -1) { break; } int x; int y; mouse_get_raw_state(&x, &y, &buttons); v11 |= buttons; } while (((v11 & 1) == 0 && (v11 & 2) == 0) || (buttons & 1) != 0 || (buttons & 2) != 0); movieStop(); moviefx_stop(); movieUpdate(); palette_set_to(black_palette); gmovie_played_list[movie] = 1; cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); if (!cursorWasHidden) { mouse_show(); } if (subtitlesEnabled) { loadColorTable("color.pal"); windowSetFont(oldFont); float r = (float)((Color2RGB(oldTextColor) & 0x7C00) >> 10) / 31.0f; float g = (float)((Color2RGB(oldTextColor) & 0x3E0) >> 5) / 31.0f; float b = (float)(Color2RGB(oldTextColor) & 0x1F) / 31.0f; windowSetTextColor(r, g, b); } win_delete(win); if ((flags & GAME_MOVIE_PAUSE_MUSIC) != 0) { gsound_background_unpause(); } if ((flags & GAME_MOVIE_FADE_OUT) != 0) { if (!subtitlesEnabled) { loadColorTable("color.pal"); } palette_fade_to(cmap); gmPaletteWasFaded = false; } gmMovieIsPlaying = false; return 0; } // 0x44EAE4 void gmPaletteFinish() { if (gmPaletteWasFaded) { palette_fade_to(cmap); gmPaletteWasFaded = false; } } // 0x44EB04 bool gmovie_has_been_played(int movie) { return gmovie_played_list[movie] == 1; } // 0x44EB14 bool gmovieIsPlaying() { return gmMovieIsPlaying; } // 0x44EB1C static char* gmovie_subtitle_func(char* movieFilePath) { // 0x596C89 static char full_path[MAX_PATH]; char* language; config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language); char* path = movieFilePath; char* separator = strrchr(path, '\\'); if (separator != NULL) { path = separator + 1; } sprintf(full_path, "text\\%s\\cuts\\%s", language, path); char* pch = strrchr(full_path, '.'); if (*pch != '\0') { *pch = '\0'; } strcpy(full_path + strlen(full_path), ".SVE"); return full_path; } ================================================ FILE: src/game/gmovie.h ================================================ #ifndef FALLOUT_GAME_GMOVIE_H_ #define FALLOUT_GAME_GMOVIE_H_ #include #include "plib/db/db.h" typedef enum GameMovieFlags { GAME_MOVIE_FADE_IN = 0x01, GAME_MOVIE_FADE_OUT = 0x02, GAME_MOVIE_STOP_MUSIC = 0x04, GAME_MOVIE_PAUSE_MUSIC = 0x08, } GameMovieFlags; typedef enum GameMovie { MOVIE_IPLOGO, MOVIE_INTRO, MOVIE_ELDER, MOVIE_VSUIT, MOVIE_AFAILED, MOVIE_ADESTROY, MOVIE_CAR, MOVIE_CARTUCCI, MOVIE_TIMEOUT, MOVIE_TANKER, MOVIE_ENCLAVE, MOVIE_DERRICK, MOVIE_ARTIMER1, MOVIE_ARTIMER2, MOVIE_ARTIMER3, MOVIE_ARTIMER4, MOVIE_CREDITS, MOVIE_COUNT, } GameMovie; int gmovie_init(); void gmovie_reset(); int gmovie_load(File* stream); int gmovie_save(File* stream); int gmovie_play(int movie, int flags); void gmPaletteFinish(); bool gmovie_has_been_played(int movie); bool gmovieIsPlaying(); #endif /* FALLOUT_GAME_GMOVIE_H_ */ ================================================ FILE: src/game/graphlib.c ================================================ #include "game/graphlib.h" #include #include "plib/color/color.h" #include "plib/gnw/debug.h" #include "plib/gnw/memory.h" static void InitTree(); static void InsertNode(int a1); static void DeleteNode(int a1); // 0x596D90 static unsigned char GreyTable[256]; // 0x596E90 int* dad; // 0x596E94 int match_length; // 0x596E98 int textsize; // 0x596E9C int* rson; // 0x596EA0 int* lson; // 0x596EA4 unsigned char* text_buf; // 0x596EA8 int codesize; // 0x596EAC int match_position; // 0x44EBC0 int HighRGB(int a1) { // TODO: Some strange bit arithmetic. int v1 = Color2RGB(a1); int r = (v1 & 0x7C00) >> 10; int g = (v1 & 0x3E0) >> 5; int b = (v1 & 0x1F); int result = g; if (r > result) { result = r; } result = result & 0xFF; if (result <= b) { result = b; } return result; } // 0x44F250 int CompLZS(unsigned char* a1, unsigned char* a2, int a3) { dad = NULL; rson = NULL; lson = NULL; text_buf = NULL; // NOTE: Original code is slightly different, it uses deep nesting or a // bunch of gotos. lson = (int*)mem_malloc(sizeof(*lson) * 4104); rson = (int*)mem_malloc(sizeof(*rson) * 4376); dad = (int*)mem_malloc(sizeof(*dad) * 4104); text_buf = (unsigned char*)mem_malloc(sizeof(*text_buf) * 4122); if (lson == NULL || rson == NULL || dad == NULL || text_buf == NULL) { debug_printf("\nGRAPHLIB: Error allocating compression buffers!\n"); if (dad != NULL) { mem_free(dad); } if (rson != NULL) { mem_free(rson); } if (lson != NULL) { mem_free(lson); } if (text_buf != NULL) { mem_free(text_buf); } return -1; } InitTree(); memset(text_buf, ' ', 4078); int count = 0; int v30 = 0; for (int index = 4078; index < 4096; index++) { text_buf[index] = *a1++; int v8 = v30++; if (v8 > a3) { break; } count++; } textsize = count; for (int index = 4077; index > 4059; index--) { InsertNode(index); } InsertNode(4078); unsigned char v29[32]; v29[1] = 0; int v3 = 4078; int v4 = 0; int v10 = 0; int v36 = 1; unsigned char v41 = 1; int rc = 0; while (count != 0) { if (count < match_length) { match_length = count; } int v11 = v36 + 1; if (match_length > 2) { v29[v36 + 1] = match_position; v29[v36 + 2] = ((match_length - 3) | ((match_position >> 4) & 0xF0)); v36 = v11 + 1; } else { match_length = 1; v29[1] |= v41; int v13 = v36++; v29[v13 + 1] = text_buf[v3]; } v41 *= 2; if (v41 == 0) { v11 = 0; if (v36 != 0) { for (;;) { v4++; *a2++ = v29[v11 + 1]; if (v4 > a3) { rc = -1; break; } v11++; if (v11 >= v36) { break; } } if (rc == -1) { break; } } codesize += v36; v29[1] = 0; v36 = 1; v41 = 1; } int v16; int v38 = match_length; for (v16 = 0; v16 < v38; v16++) { unsigned char v34 = *a1++; int v17 = v30++; if (v17 >= a3) { break; } DeleteNode(v10); unsigned char* v19 = text_buf + v10; text_buf[v10] = v34; if (v10 < 17) { v19[4096] = v34; } v3 = (v3 + 1) & 0xFFF; v10 = (v10 + 1) & 0xFFF; InsertNode(v3); } for (; v16 < v38; v16++) { DeleteNode(v10); v3 = (v3 + 1) & 0xFFF; v10 = (v10 + 1) & 0xFFF; if (--count != 0) { InsertNode(v3); } } } if (rc != -1) { for (int v23 = 0; v23 < v36; v23++) { v4++; v10++; *a2++ = v29[v23 + 1]; if (v10 > a3) { rc = -1; break; } } codesize += v36; } mem_free(lson); mem_free(rson); mem_free(dad); mem_free(text_buf); if (rc == -1) { v4 = -1; } return v4; } // 0x44F5F0 static void InitTree() { for (int index = 4097; index < 4353; index++) { rson[index] = 4096; } for (int index = 0; index < 4096; index++) { dad[index] = 4096; } } // 0x44F63C static void InsertNode(int a1) { lson[a1] = 4096; rson[a1] = 4096; match_length = 0; unsigned char* v2 = text_buf + a1; int v21 = 4097 + text_buf[a1]; int v5 = 1; for (;;) { int v6 = v21; if (v5 < 0) { if (lson[v6] == 4096) { lson[v6] = a1; dad[a1] = v21; return; } v21 = lson[v6]; } else { if (rson[v6] == 4096) { rson[v6] = a1; dad[a1] = v21; return; } v21 = rson[v6]; } int v9; unsigned char* v10 = v2 + 1; int v11 = v21 + 1; for (v9 = 1; v9 < 18; v9++) { v5 = *v10 - text_buf[v11]; if (v5 != 0) { break; } v10++; v11++; } if (v9 > match_length) { match_length = v9; match_position = v21; if (v9 >= 18) { break; } } } dad[a1] = dad[v21]; lson[a1] = lson[v21]; rson[a1] = rson[v21]; dad[lson[v21]] = a1; dad[rson[v21]] = a1; if (rson[dad[v21]] == v21) { rson[dad[v21]] = a1; } else { lson[dad[v21]] = a1; } dad[v21] = 4096; } // 0x44F7EC static void DeleteNode(int a1) { if (dad[a1] != 4096) { int v5; if (rson[a1] == 4096) { v5 = lson[a1]; } else { if (lson[a1] == 4096) { v5 = rson[a1]; } else { v5 = lson[a1]; if (rson[v5] != 4096) { do { v5 = rson[v5]; } while (rson[v5] != 4096); rson[dad[v5]] = lson[v5]; dad[lson[v5]] = dad[v5]; lson[v5] = lson[a1]; dad[lson[a1]] = v5; } rson[v5] = rson[a1]; dad[rson[a1]] = v5; } } dad[v5] = dad[a1]; if (rson[dad[a1]] == a1) { rson[dad[a1]] = v5; } else { lson[dad[a1]] = v5; } dad[a1] = 4096; } } // 0x44F92C int DecodeLZS(unsigned char* src, unsigned char* dest, int length) { text_buf = (unsigned char*)mem_malloc(sizeof(*text_buf) * 4122); if (text_buf == NULL) { debug_printf("\nGRAPHLIB: Error allocating decompression buffer!\n"); return -1; } int v8 = 4078; memset(text_buf, ' ', v8); int v21 = 0; int index = 0; while (index < length) { v21 >>= 1; if ((v21 & 0x100) == 0) { v21 = *src++; v21 |= 0xFF00; } if ((v21 & 0x01) == 0) { int v10 = *src++; int v11 = *src++; v10 |= (v11 & 0xF0) << 4; v11 &= 0x0F; v11 += 2; for (int v16 = 0; v16 <= v11; v16++) { int v17 = (v10 + v16) & 0xFFF; unsigned char ch = text_buf[v17]; text_buf[v8] = ch; *dest++ = ch; v8 = (v8 + 1) & 0xFFF; index++; if (index >= length) { break; } } } else { unsigned char ch = *src++; text_buf[v8] = ch; *dest++ = ch; v8 = (v8 + 1) & 0xFFF; index++; } } mem_free(text_buf); return 0; } // 0x44FA78 void InitGreyTable(int a1, int a2) { if (a1 >= 0 && a2 <= 255) { for (int index = a1; index <= a2; index++) { // NOTE: The only way to explain so much calls to [Color2RGB] with // the same repeated pattern is by the use of min/max macros. int v1 = max((Color2RGB(index) & 0x7C00) >> 10, max((Color2RGB(index) & 0x3E0) >> 5, Color2RGB(index) & 0x1F)); int v2 = min((Color2RGB(index) & 0x7C00) >> 10, min((Color2RGB(index) & 0x3E0) >> 5, Color2RGB(index) & 0x1F)); int v3 = v1 + v2; int v4 = (int)((double)v3 * 240.0 / 510.0); int paletteIndex = ((v4 & 0xFF) << 10) | ((v4 & 0xFF) << 5) | (v4 & 0xFF); GreyTable[index] = colorTable[paletteIndex]; } } } // 0x44FC40 void grey_buf(unsigned char* buffer, int width, int height, int pitch) { unsigned char* ptr = buffer; int skip = pitch - width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned char c = *ptr; *ptr++ = GreyTable[c]; } ptr += skip; } } ================================================ FILE: src/game/graphlib.h ================================================ #ifndef FALLOUT_GAME_GRAPHLIB_H_ #define FALLOUT_GAME_GRAPHLIB_H_ extern int* dad; extern int match_length; extern int textsize; extern int* rson; extern int* lson; extern unsigned char* text_buf; extern int codesize; extern int match_position; int HighRGB(int a1); int CompLZS(unsigned char* a1, unsigned char* a2, int a3); int DecodeLZS(unsigned char* a1, unsigned char* a2, int a3); void InitGreyTable(int a1, int a2); void grey_buf(unsigned char* surface, int width, int height, int pitch); #endif /* FALLOUT_GAME_GRAPHLIB_H_ */ ================================================ FILE: src/game/gsound.c ================================================ #include "game/gsound.h" #include #include #define WIN32_LEAN_AND_MEAN #include #include "game/anim.h" #include "int/audio.h" #include "int/audiof.h" #include "game/combat.h" #include "plib/gnw/input.h" #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "game/gconfig.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "int/movie.h" #include "game/object.h" #include "game/proto.h" #include "game/queue.h" #include "game/roll.h" #include "game/sfxcache.h" #include "game/stat.h" #include "plib/gnw/gnw.h" #include "game/worldmap.h" static void gsound_bkg_proc(); static int gsound_open(const char* fname, int access, ...); static long gsound_compressed_tell(int handle); static int gsound_write(int handle, const void* buf, unsigned int size); static int gsound_close(int handle); static int gsound_read(int handle, void* buf, unsigned int size); static long gsound_seek(int handle, long offset, int origin); static long gsound_tell(int handle); static long gsound_filesize(int handle); static bool gsound_compressed_query(char* filePath); static void gsound_internal_speech_callback(void* userData, int a2); static void gsound_internal_background_callback(void* userData, int a2); static void gsound_internal_effect_callback(void* userData, int a2); static int gsound_background_allocate(Sound** out_s, int a2, int a3); static int gsound_background_find_with_copy(char* dest, const char* src); static int gsound_background_find_dont_copy(char* dest, const char* src); static int gsound_speech_find_dont_copy(char* dest, const char* src); static void gsound_background_remove_last_copy(); static int gsound_background_start(); static int gsound_speech_start(); static int gsound_get_music_path(char** out_value, const char* key); static Sound* gsound_get_sound_ready_for_effect(); static bool gsound_file_exists_f(const char* fname); static int gsound_file_exists_db(const char* path); static int gsound_setup_paths(); // TODO: Remove. // 0x5035BC char _aSoundSfx[] = "sound\\sfx\\"; // TODO: Remove. // 0x5035C8 char _aSoundMusic_0[] = "sound\\music\\"; // TODO: Remove. // 0x5035D8 char _aSoundSpeech_0[] = "sound\\speech\\"; // 0x518E30 static bool gsound_initialized = false; // 0x518E34 static bool gsound_debug = false; // 0x518E38 static bool gsound_background_enabled = false; // 0x518E3C static int _gsound_background_df_vol = 0; // 0x518E40 static int gsound_background_fade = 0; // 0x518E44 static bool gsound_speech_enabled = false; // 0x518E48 static bool gsound_sfx_enabled = false; // number of active effects (max 4) // // 0x518E4C static int gsound_active_effect_counter; // 0x518E50 static Sound* gsound_background_tag = NULL; // 0x518E54 static Sound* gsound_speech_tag = NULL; // 0x518E58 static SoundEndCallback* gsound_background_callback_fp = NULL; // 0x518E5C static SoundEndCallback* gsound_speech_callback_fp = NULL; // 0x518E60 static char snd_lookup_weapon_type[WEAPON_SOUND_EFFECT_COUNT] = { 'R', // Ready 'A', // Attack 'O', // Out of ammo 'F', // Firing 'H', // Hit }; // 0x518E65 static char snd_lookup_scenery_action[SCENERY_SOUND_EFFECT_COUNT] = { 'O', // Open 'C', // Close 'L', // Lock 'N', // Unlock 'U', // Use }; // 0x518E6C static int background_storage_requested = -1; // 0x518E70 static int background_loop_requested = -1; // 0x518E74 static char sound_sfx_path[] = "sound\\sfx\\"; // 0x518E78 static char* sound_music_path1 = _aSoundMusic_0; // 0x518E7C static char* sound_music_path2 = _aSoundMusic_0; // 0x518E80 static char* sound_speech_path = _aSoundSpeech_0; // 0x518E84 static int master_volume = VOLUME_MAX; // 0x518E88 static int background_volume = VOLUME_MAX; // 0x518E8C static int speech_volume = VOLUME_MAX; // 0x518E90 static int sndfx_volume = VOLUME_MAX; // 0x518E94 static int detectDevices = -1; // 0x596EB0 static char background_fname_copied[MAX_PATH]; // 0x596FB5 static char sfx_file_name[13]; // NOTE: I'm mot sure about it's size. Why not MAX_PATH? // // 0x596FC2 static char background_fname_requested[270]; // 0x44FC70 int gsound_init() { if (gsound_initialized) { if (gsound_debug) { debug_printf("Trying to initialize gsound twice.\n"); } return -1; } bool initialize; configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_INITIALIZE_KEY, &initialize); if (!initialize) { return 0; } configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEBUG_KEY, &gsound_debug); if (gsound_debug) { debug_printf("Initializing sound system..."); } if (gsound_get_music_path(&sound_music_path1, GAME_CONFIG_MUSIC_PATH1_KEY) != 0) { return -1; } if (gsound_get_music_path(&sound_music_path2, GAME_CONFIG_MUSIC_PATH2_KEY) != 0) { return -1; } if (strlen(sound_music_path1) > 247 || strlen(sound_music_path2) > 247) { if (gsound_debug) { debug_printf("Music paths way too long.\n"); } return -1; } // gsound_setup_paths if (gsound_setup_paths() != 0) { return -1; } soundRegisterAlloc(mem_malloc, mem_realloc, mem_free); // initialize direct sound if (soundInit(detectDevices, 24, 0x8000, 0x8000, 22050) != 0) { if (gsound_debug) { debug_printf("failed!\n"); } return -1; } if (gsound_debug) { debug_printf("success.\n"); } initAudiof(gsound_compressed_query); initAudio(gsound_compressed_query); int cacheSize; config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_CACHE_SIZE_KEY, &cacheSize); if (cacheSize >= 0x40000) { debug_printf("\n!!! Config file needs adustment. Please remove the "); debug_printf("cache_size line and run fallout again. This will reset "); debug_printf("cache_size to the new default, which is expressed in K.\n"); return -1; } if (sfxc_init(cacheSize << 10, sound_sfx_path) != 0) { if (gsound_debug) { debug_printf("Unable to initialize sound effects cache.\n"); } } if (soundSetDefaultFileIO(gsound_open, gsound_close, gsound_read, gsound_write, gsound_seek, gsound_tell, gsound_filesize) != 0) { if (gsound_debug) { debug_printf("Failure setting sound I/O calls.\n"); } return -1; } add_bk_process(gsound_bkg_proc); gsound_initialized = true; // SOUNDS bool sounds = 0; configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SOUNDS_KEY, &sounds); if (gsound_debug) { debug_printf("Sounds are "); } if (sounds) { // NOTE: Uninline. gsound_sfx_enable(); } else { if (gsound_debug) { debug_printf(" not "); } } if (gsound_debug) { debug_printf("on.\n"); } // MUSIC bool music = 0; configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_KEY, &music); if (gsound_debug) { debug_printf("Music is "); } if (music) { // NOTE: Uninline. gsound_background_enable(); } else { if (gsound_debug) { debug_printf(" not "); } } if (gsound_debug) { debug_printf("on.\n"); } // SPEEECH bool speech = 0; configGetBool(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_KEY, &speech); if (gsound_debug) { debug_printf("Speech is "); } if (speech) { // NOTE: Uninline. gsound_speech_enable(); } else { if (gsound_debug) { debug_printf(" not "); } } if (gsound_debug) { debug_printf("on.\n"); } config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, &master_volume); gsound_set_master_volume(master_volume); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, &background_volume); gsound_background_volume_set(background_volume); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, &sndfx_volume); gsound_set_sfx_volume(sndfx_volume); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, &speech_volume); gsound_speech_volume_set(speech_volume); // NOTE: Uninline. gsound_background_fade_set(0); background_fname_requested[0] = '\0'; return 0; } // 0x450164 void gsound_reset() { if (!gsound_initialized) { return; } if (gsound_debug) { debug_printf("Resetting sound system..."); } // NOTE: Uninline. gsound_speech_stop(); if (_gsound_background_df_vol) { // NOTE: Uninline. gsound_background_enable(); } gsound_background_stop(); // NOTE: Uninline. gsound_background_fade_set(0); soundFlushAllSounds(); sfxc_flush(); gsound_active_effect_counter = 0; if (gsound_debug) { debug_printf("done.\n"); } return; } // 0x450244 int gsound_exit() { if (!gsound_initialized) { return -1; } remove_bk_process(gsound_bkg_proc); // NOTE: Uninline. gsound_speech_stop(); gsound_background_stop(); gsound_background_remove_last_copy(); soundClose(); sfxc_exit(); audiofClose(); audioClose(); gsound_initialized = false; return 0; } // NOTE: Inlined. // // 0x4502BC void gsound_sfx_enable() { if (gsound_initialized) { gsound_sfx_enabled = true; } } // NOTE: Inlined. // // 0x4502D0 void gsound_sfx_disable() { if (gsound_initialized) { gsound_sfx_enabled = false; } } // 0x4502E4 int gsound_sfx_is_enabled() { return gsound_sfx_enabled; } // 0x4502EC int gsound_set_master_volume(int volume) { if (!gsound_initialized) { return -1; } if (volume < VOLUME_MIN && volume > VOLUME_MAX) { if (gsound_debug) { debug_printf("Requested master volume out of range.\n"); } return -1; } if (_gsound_background_df_vol && volume != 0 && gsound_background_volume_get() != 0) { // NOTE: Uninline. gsound_background_enable(); _gsound_background_df_vol = 0; } if (soundSetMasterVolume(volume) != 0) { if (gsound_debug) { debug_printf("Error setting master sound volume.\n"); } return -1; } master_volume = volume; if (gsound_background_enabled && volume == 0) { // NOTE: Uninline. gsound_background_disable(); _gsound_background_df_vol = 1; } return 0; } // 0x450410 int gsound_get_master_volume() { return master_volume; } // 0x450418 int gsound_set_sfx_volume(int volume) { if (!gsound_initialized || volume < VOLUME_MIN || volume > VOLUME_MAX) { if (gsound_debug) { debug_printf("Error setting sfx volume.\n"); } return -1; } sndfx_volume = volume; return 0; } // 0x450454 int gsound_get_sfx_volume() { return sndfx_volume; } // NOTE: Inlined. // // 0x45045C void gsound_background_disable() { if (gsound_initialized) { if (gsound_background_enabled) { gsound_background_stop(); movieSetVolume(0); gsound_background_enabled = false; } } } // NOTE: Inlined. // // 0x450488 void gsound_background_enable() { if (gsound_initialized) { if (!gsound_background_enabled) { movieSetVolume((int)(background_volume * 0.94)); gsound_background_enabled = true; gsound_background_restart_last(12); } } } // 0x4504D4 int gsound_background_is_enabled() { return gsound_background_enabled; } // 0x4504DC void gsound_background_volume_set(int volume) { if (!gsound_initialized) { return; } if (volume < VOLUME_MIN || volume > VOLUME_MAX) { if (gsound_debug) { debug_printf("Requested background volume out of range.\n"); } return; } background_volume = volume; if (_gsound_background_df_vol) { // NOTE: Uninline. gsound_background_enable(); _gsound_background_df_vol = 0; } if (gsound_background_enabled) { movieSetVolume((int)(volume * 0.94)); } if (gsound_background_enabled) { if (gsound_background_tag != NULL) { soundVolume(gsound_background_tag, (int)(background_volume * 0.94)); } } if (gsound_background_enabled) { if (volume == 0 || gsound_get_master_volume() == 0) { // NOTE: Uninline. gsound_background_disable(); _gsound_background_df_vol = 1; } } } // 0x450618 int gsound_background_volume_get() { return background_volume; } // 0x450620 int gsound_background_volume_get_set(int volume) { int oldMusicVolume; // NOTE: Uninline. oldMusicVolume = gsound_background_volume_get(); gsound_background_volume_set(volume); return oldMusicVolume; } // NOTE: Inlined. // // 0x450630 void gsound_background_fade_set(int value) { gsound_background_fade = value; } // NOTE: Inlined. // // 0x450638 int gsound_background_fade_get() { return gsound_background_fade; } // NOTE: Unused. // // 0x450640 int gsound_background_fade_get_set(int value) { int oldValue; // NOTE: Uninline. oldValue = gsound_background_fade_get(); // NOTE: Uninline. gsound_background_fade_set(value); return oldValue; } // 0x450650 void gsound_background_callback_set(SoundEndCallback* callback) { gsound_background_callback_fp = callback; } // 0x450658 SoundEndCallback* gsound_background_callback_get() { return gsound_background_callback_fp; } // 0x450660 SoundEndCallback* gsound_background_callback_get_set(SoundEndCallback* callback) { SoundEndCallback* oldCallback; // NOTE: Uninline. oldCallback = gsound_background_callback_get(); // NOTE: Uninline. gsound_background_callback_set(callback); return oldCallback; } // NOTE: There are no references to this function. // // 0x450670 int gsound_background_length_get() { return soundLength(gsound_background_tag); } // [fileName] is base file name, without path and extension. // // 0x45067C int gsound_background_play(const char* fileName, int a2, int a3, int a4) { int rc; background_storage_requested = a3; background_loop_requested = a4; strcpy(background_fname_requested, fileName); if (!gsound_initialized) { return -1; } if (!gsound_background_enabled) { return -1; } if (gsound_debug) { debug_printf("Loading background sound file %s%s...", fileName, ".acm"); } gsound_background_stop(); rc = gsound_background_allocate(&gsound_background_tag, a3, a4); if (rc != 0) { if (gsound_debug) { debug_printf("failed because sound could not be allocated.\n"); } gsound_background_tag = NULL; return -1; } rc = soundSetFileIO(gsound_background_tag, audiofOpen, audiofCloseFile, audiofRead, NULL, audiofSeek, gsound_compressed_tell, audiofFileSize); if (rc != 0) { if (gsound_debug) { debug_printf("failed because file IO could not be set for compression.\n"); } soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } rc = soundSetChannel(gsound_background_tag, 3); if (rc != 0) { if (gsound_debug) { debug_printf("failed because the channel could not be set.\n"); } soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } char path[MAX_PATH + 1]; if (a3 == 13) { rc = gsound_background_find_dont_copy(path, fileName); } else if (a3 == 14) { rc = gsound_background_find_with_copy(path, fileName); } if (rc != SOUND_NO_ERROR) { if (gsound_debug) { debug_printf("'failed because the file could not be found.\n"); } soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } if (a4 == 16) { rc = soundLoop(gsound_background_tag, 0xFFFF); if (rc != SOUND_NO_ERROR) { if (gsound_debug) { debug_printf("failed because looping could not be set.\n"); } soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } } rc = soundSetCallback(gsound_background_tag, gsound_internal_background_callback, NULL); if (rc != SOUND_NO_ERROR) { if (gsound_debug) { debug_printf("soundSetCallback failed for background sound\n"); } } if (a2 == 11) { rc = soundSetReadLimit(gsound_background_tag, 0x40000); if (rc != SOUND_NO_ERROR) { if (gsound_debug) { debug_printf("unable to set read limit "); } } } rc = soundLoad(gsound_background_tag, path); if (rc != SOUND_NO_ERROR) { if (gsound_debug) { debug_printf("failed on call to soundLoad.\n"); } soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } if (a2 != 11) { rc = soundSetReadLimit(gsound_background_tag, 0x40000); if (rc != 0) { if (gsound_debug) { debug_printf("unable to set read limit "); } } } if (a2 == 10) { return 0; } rc = gsound_background_start(); if (rc != 0) { if (gsound_debug) { debug_printf("failed starting to play.\n"); } soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } if (gsound_debug) { debug_printf("succeeded.\n"); } return 0; } // 0x450A08 int gsound_background_play_level_music(const char* a1, int a2) { return gsound_background_play(a1, a2, 14, 16); } // 0x450A1C int gsound_background_play_preloaded() { if (!gsound_initialized) { return -1; } if (!gsound_background_enabled) { return -1; } if (gsound_background_tag == NULL) { return -1; } if (soundPlaying(gsound_background_tag)) { return -1; } if (soundPaused(gsound_background_tag)) { return -1; } if (soundDone(gsound_background_tag)) { return -1; } if (gsound_background_start() != 0) { soundDelete(gsound_background_tag); gsound_background_tag = NULL; return -1; } return 0; } // 0x450AB4 void gsound_background_stop() { if (gsound_initialized && gsound_background_enabled && gsound_background_tag) { if (gsound_background_fade) { if (soundFade(gsound_background_tag, 2000, 0) == 0) { gsound_background_tag = NULL; return; } } soundDelete(gsound_background_tag); gsound_background_tag = NULL; } } // 0x450B0C void gsound_background_restart_last(int value) { if (background_fname_requested[0] != '\0') { if (gsound_background_play(background_fname_requested, value, background_storage_requested, background_loop_requested) != 0) { if (gsound_debug) debug_printf(" background restart failed "); } } } // 0x450B50 void gsound_background_pause() { if (gsound_background_tag != NULL) { soundPause(gsound_background_tag); } } // 0x450B64 void gsound_background_unpause() { if (gsound_background_tag != NULL) { soundUnpause(gsound_background_tag); } } // NOTE: Inlined. // // 0x450B78 void gsound_speech_disable() { if (gsound_initialized) { if (gsound_speech_enabled) { gsound_speech_stop(); gsound_speech_enabled = false; } } } // NOTE: Inlined. // // 0x450BC0 void gsound_speech_enable() { if (gsound_initialized) { if (!gsound_speech_enabled) { gsound_speech_enabled = true; } } } // 0x450BE0 int gsound_speech_is_enabled() { return gsound_speech_enabled; } // 0x450BE8 void gsound_speech_volume_set(int volume) { if (!gsound_initialized) { return; } if (volume < VOLUME_MIN || volume > VOLUME_MAX) { if (gsound_debug) { debug_printf("Requested speech volume out of range.\n"); } return; } speech_volume = volume; if (gsound_speech_enabled) { if (gsound_speech_tag != NULL) { soundVolume(gsound_speech_tag, (int)(volume * 0.69)); } } } // 0x450C5C int gsound_speech_volume_get() { return speech_volume; } // 0x450C64 int gsound_speech_volume_get_set(int volume) { int oldVolume = speech_volume; gsound_speech_volume_set(volume); return oldVolume; } // 0x450C74 void gsound_speech_callback_set(SoundEndCallback* callback) { gsound_speech_callback_fp = callback; } // 0x450C7C SoundEndCallback* gsound_speech_callback_get() { return gsound_speech_callback_fp; } // 0x450C84 SoundEndCallback* gsound_speech_callback_get_set(SoundEndCallback* callback) { SoundEndCallback* oldCallback; // NOTE: Uninline. oldCallback = gsound_speech_callback_get(); // NOTE: Uninline. gsound_speech_callback_set(callback); return oldCallback; } // 0x450C94 int gsound_speech_length_get() { return soundLength(gsound_speech_tag); } // 0x450CA0 int gsound_speech_play(const char* fname, int a2, int a3, int a4) { char path[MAX_PATH + 1]; if (!gsound_initialized) { return -1; } if (!gsound_speech_enabled) { return -1; } if (gsound_debug) { debug_printf("Loading speech sound file %s%s...", fname, ".ACM"); } // uninline gsound_speech_stop(); if (gsound_background_allocate(&gsound_speech_tag, a3, a4)) { if (gsound_debug) { debug_printf("failed because sound could not be allocated.\n"); } gsound_speech_tag = NULL; return -1; } if (soundSetFileIO(gsound_speech_tag, &audioOpen, &audioCloseFile, &audioRead, NULL, &audioSeek, &gsound_compressed_tell, &audioFileSize)) { if (gsound_debug) { debug_printf("failed because file IO could not be set for compression.\n"); } soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; return -1; } if (gsound_speech_find_dont_copy(path, fname)) { if (gsound_debug) { debug_printf("failed because the file could not be found.\n"); } soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; return -1; } if (a4 == 16) { if (soundLoop(gsound_speech_tag, 0xFFFF)) { if (gsound_debug) { debug_printf("failed because looping could not be set.\n"); } soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; return -1; } } if (soundSetCallback(gsound_speech_tag, gsound_internal_speech_callback, NULL)) { if (gsound_debug) { debug_printf("soundSetCallback failed for speech sound\n"); } } if (a2 == 11) { if (soundSetReadLimit(gsound_speech_tag, 0x40000)) { if (gsound_debug) { debug_printf("unable to set read limit "); } } } if (soundLoad(gsound_speech_tag, path)) { if (gsound_debug) { debug_printf("failed on call to soundLoad.\n"); } soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; return -1; } if (a2 != 11) { if (soundSetReadLimit(gsound_speech_tag, 0x40000)) { if (gsound_debug) { debug_printf("unable to set read limit "); } } } if (a2 == 10) { return 0; } if (gsound_speech_start()) { if (gsound_debug) { debug_printf("failed starting to play.\n"); } soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; return -1; } if (gsound_debug) { debug_printf("succeeded.\n"); } return 0; } // 0x450F8C int gsound_speech_play_preloaded() { if (!gsound_initialized) { return -1; } if (!gsound_speech_enabled) { return -1; } if (gsound_speech_tag == NULL) { return -1; } if (soundPlaying(gsound_speech_tag)) { return -1; } if (soundPaused(gsound_speech_tag)) { return -1; } if (soundDone(gsound_speech_tag)) { return -1; } if (gsound_speech_start() != 0) { soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; return -1; } return 0; } // 0x451024 void gsound_speech_stop() { if (gsound_initialized && gsound_speech_enabled) { if (gsound_speech_tag != NULL) { soundDelete(gsound_speech_tag); gsound_speech_tag = NULL; } } } // 0x451054 void gsound_speech_pause() { if (gsound_speech_tag != NULL) { soundPause(gsound_speech_tag); } } // 0x451068 void gsound_speech_unpause() { if (gsound_speech_tag != NULL) { soundUnpause(gsound_speech_tag); } } // 0x45108C int gsound_play_sfx_file_volume(const char* a1, int a2) { Sound* v1; if (!gsound_initialized) { return -1; } if (!gsound_sfx_enabled) { return -1; } v1 = gsound_load_sound_volume(a1, NULL, a2); if (v1 == NULL) { return -1; } soundPlay(v1); return 0; } // 0x4510DC Sound* gsound_load_sound(const char* name, Object* object) { if (!gsound_initialized) { return NULL; } if (!gsound_sfx_enabled) { return NULL; } if (gsound_debug) { debug_printf("Loading sound file %s%s...", name, ".ACM"); } if (gsound_active_effect_counter >= SOUND_EFFECTS_MAX_COUNT) { if (gsound_debug) { debug_printf("failed because there are already %d active effects.\n", gsound_active_effect_counter); } return NULL; } Sound* sound = gsound_get_sound_ready_for_effect(); if (sound == NULL) { if (gsound_debug) { debug_printf("failed.\n"); } return NULL; } ++gsound_active_effect_counter; char path[MAX_PATH]; sprintf(path, "%s%s%s", sound_sfx_path, name, ".ACM"); if (soundLoad(sound, path) == 0) { if (gsound_debug) { debug_printf("succeeded.\n"); } return sound; } if (object != NULL) { if (FID_TYPE(object->fid) == OBJ_TYPE_CRITTER && (name[0] == 'H' || name[0] == 'N')) { char v9 = name[1]; if (v9 == 'A' || v9 == 'F' || v9 == 'M') { if (v9 == 'A') { if (critterGetStat(object, STAT_GENDER)) { v9 = 'F'; } else { v9 = 'M'; } } } sprintf(path, "%sH%cXXXX%s%s", sound_sfx_path, v9, name + 6, ".ACM"); if (gsound_debug) { debug_printf("tyring %s ", path + strlen(sound_sfx_path)); } if (soundLoad(sound, path) == 0) { if (gsound_debug) { debug_printf("succeeded (with alias).\n"); } return sound; } if (v9 == 'F') { sprintf(path, "%sHMXXXX%s%s", sound_sfx_path, name + 6, ".ACM"); if (gsound_debug) { debug_printf("tyring %s ", path + strlen(sound_sfx_path)); } if (soundLoad(sound, path) == 0) { if (gsound_debug) { debug_printf("succeeded (with male alias).\n"); } return sound; } } } } if (strncmp(name, "MALIEU", 6) == 0 || strncmp(name, "MAMTN2", 6) == 0) { sprintf(path, "%sMAMTNT%s%s", sound_sfx_path, name + 6, ".ACM"); if (gsound_debug) { debug_printf("tyring %s ", path + strlen(sound_sfx_path)); } if (soundLoad(sound, path) == 0) { if (gsound_debug) { debug_printf("succeeded (with alias).\n"); } return sound; } } --gsound_active_effect_counter; soundDelete(sound); if (gsound_debug) { debug_printf("failed.\n"); } return NULL; } // 0x45145C Sound* gsound_load_sound_volume(const char* name, Object* object, int volume) { Sound* sound = gsound_load_sound(name, object); if (sound != NULL) { soundVolume(sound, (volume * sndfx_volume) / VOLUME_MAX); } return sound; } // 0x45148C void gsound_delete_sfx(Sound* sound) { if (!gsound_initialized) { return; } if (!gsound_sfx_enabled) { return; } if (soundPlaying(sound)) { if (gsound_debug) { debug_printf("Trying to manually delete a sound effect after it has started playing.\n"); } return; } if (soundDelete(sound) != 0) { if (gsound_debug) { debug_printf("Unable to delete sound effect -- active effect counter may get out of sync.\n"); } return; } --gsound_active_effect_counter; } // 0x4514F0 int gsnd_anim_sound(Sound* sound, void* a2) { if (!gsound_initialized) { return 0; } if (!gsound_sfx_enabled) { return 0; } if (sound == NULL) { return 0; } soundPlay(sound); return 0; } // 0x451510 int gsound_play_sound(Sound* sound) { if (!gsound_initialized) { return -1; } if (!gsound_sfx_enabled) { return -1; } if (sound == NULL) { return -1; } soundPlay(sound); return 0; } // Probably returns volume dependending on the distance between the specified // object and dude. // // 0x451534 int gsound_compute_relative_volume(Object* obj) { int type; int v3; Object* v7; Rect v12; Rect v14; Rect iso_win_rect; int distance; int perception; v3 = 0x7FFF; if (obj) { type = FID_TYPE(obj->fid); if (type == 0 || type == 1 || type == 2) { v7 = obj_top_environment(obj); if (!v7) { v7 = obj; } obj_bound(v7, &v14); win_get_rect(display_win, &iso_win_rect); if (rect_inside_bound(&v14, &iso_win_rect, &v12) == -1) { distance = obj_dist(v7, obj_dude); perception = critterGetStat(obj_dude, STAT_PERCEPTION); if (distance > perception) { if (distance < 2 * perception) { v3 = 0x7FFF - 0x5554 * (distance - perception) / perception; } else { v3 = 0x2AAA; } } else { v3 = 0x7FFF; } } } } return v3; } // sfx_build_char_name // 0x451604 char* gsnd_build_character_sfx_name(Object* a1, int anim, int extra) { char v7[13]; char v8; char v9; if (art_get_base_name(FID_TYPE(a1->fid), a1->fid & 0xFFF, v7) == -1) { return NULL; } if (anim == ANIM_TAKE_OUT) { if (art_get_code(anim, extra, &v8, &v9) == -1) { return NULL; } } else { if (art_get_code(anim, (a1->fid & 0xF000) >> 12, &v8, &v9) == -1) { return NULL; } } // TODO: Check. if (anim == ANIM_FALL_FRONT || anim == ANIM_FALL_BACK) { if (extra == CHARACTER_SOUND_EFFECT_PASS_OUT) { v8 = 'Y'; } else if (extra == CHARACTER_SOUND_EFFECT_DIE) { v8 = 'Z'; } } else if ((anim == ANIM_THROW_PUNCH || anim == ANIM_KICK_LEG) && extra == CHARACTER_SOUND_EFFECT_CONTACT) { v8 = 'Z'; } sprintf(sfx_file_name, "%s%c%c", v7, v8, v9); strupr(sfx_file_name); return sfx_file_name; } // sfx_build_ambient_name // 0x4516F0 char* gsnd_build_ambient_sfx_name(const char* a1) { sprintf(sfx_file_name, "A%6s%1d", a1, 1); strupr(sfx_file_name); return sfx_file_name; } // sfx_build_interface_name // 0x451718 char* gsnd_build_interface_sfx_name(const char* a1) { sprintf(sfx_file_name, "N%6s%1d", a1, 1); strupr(sfx_file_name); return sfx_file_name; } // sfx_build_weapon_name // 0x451760 char* gsnd_build_weapon_sfx_name(int effectType, Object* weapon, int hitMode, Object* target) { int v6; char weaponSoundCode; char effectTypeCode; char materialCode; Proto* proto; weaponSoundCode = item_w_sound_id(weapon); effectTypeCode = snd_lookup_weapon_type[effectType]; if (effectType != WEAPON_SOUND_EFFECT_READY && effectType != WEAPON_SOUND_EFFECT_OUT_OF_AMMO) { if (hitMode != HIT_MODE_LEFT_WEAPON_PRIMARY && hitMode != HIT_MODE_RIGHT_WEAPON_PRIMARY && hitMode != HIT_MODE_PUNCH) { v6 = 2; } else { v6 = 1; } } else { v6 = 1; } if (effectTypeCode != 'H' || target == NULL || item_w_is_grenade(weapon)) { materialCode = 'X'; } else { const int type = FID_TYPE(target->fid); int material; switch (type) { case OBJ_TYPE_ITEM: proto_ptr(target->pid, &proto); material = proto->item.material; break; case OBJ_TYPE_SCENERY: proto_ptr(target->pid, &proto); material = proto->scenery.field_2C; break; case OBJ_TYPE_WALL: proto_ptr(target->pid, &proto); material = proto->wall.material; break; default: material = -1; break; } switch (material) { case MATERIAL_TYPE_GLASS: case MATERIAL_TYPE_METAL: case MATERIAL_TYPE_PLASTIC: materialCode = 'M'; break; case MATERIAL_TYPE_WOOD: materialCode = 'W'; break; case MATERIAL_TYPE_DIRT: case MATERIAL_TYPE_STONE: case MATERIAL_TYPE_CEMENT: materialCode = 'S'; break; default: materialCode = 'F'; break; } } sprintf(sfx_file_name, "W%c%c%1d%cXX%1d", effectTypeCode, weaponSoundCode, v6, materialCode, 1); strupr(sfx_file_name); return sfx_file_name; } // sfx_build_scenery_name // 0x451898 char* gsnd_build_scenery_sfx_name(int actionType, int action, const char* name) { char actionTypeCode = actionType == SOUND_EFFECT_ACTION_TYPE_PASSIVE ? 'P' : 'A'; char actionCode = snd_lookup_scenery_action[action]; sprintf(sfx_file_name, "S%c%c%4s%1d", actionTypeCode, actionCode, name, 1); strupr(sfx_file_name); return sfx_file_name; } // sfx_build_open_name // 0x4518D char* gsnd_build_open_sfx_name(Object* object, int action) { if (FID_TYPE(object->fid) == OBJ_TYPE_SCENERY) { char scenerySoundId; Proto* proto; if (proto_ptr(object->pid, &proto) != -1) { scenerySoundId = proto->scenery.field_34; } else { scenerySoundId = 'A'; } sprintf(sfx_file_name, "S%cDOORS%c", snd_lookup_scenery_action[action], scenerySoundId); } else { Proto* proto; proto_ptr(object->pid, &proto); sprintf(sfx_file_name, "I%cCNTNR%c", snd_lookup_scenery_action[action], proto->item.field_80); } strupr(sfx_file_name); return sfx_file_name; } // 0x451970 void gsound_red_butt_press(int btn, int keyCode) { gsound_play_sfx_file("ib1p1xx1"); } // 0x451978 void gsound_red_butt_release(int btn, int keyCode) { gsound_play_sfx_file("ib1lu1x1"); } // 0x451980 void gsound_toggle_butt_press(int btn, int keyCode) { gsound_play_sfx_file("toggle"); } // NOTE: Uncollapsed from 0x451980. // // 0x451980 void gsound_toggle_butt_release(int btn, int keyCode) { gsound_play_sfx_file("toggle"); } // 0x451988 void gsound_med_butt_press(int btn, int keyCode) { gsound_play_sfx_file("ib2p1xx1"); } // 0x451990 void gsound_med_butt_release(int btn, int keyCode) { gsound_play_sfx_file("ib2lu1x1"); } // 0x451998 void gsound_lrg_butt_press(int btn, int keyCode) { gsound_play_sfx_file("ib3p1xx1"); } // 0x4519A0 void gsound_lrg_butt_release(int btn, int keyCode) { gsound_play_sfx_file("ib3lu1x1"); } // 0x4519A8 int gsound_play_sfx_file(const char* name) { if (!gsound_initialized) { return -1; } if (!gsound_sfx_enabled) { return -1; } Sound* sound = gsound_load_sound(name, NULL); if (sound == NULL) { return -1; } soundPlay(sound); return 0; } // 0x451A00 static void gsound_bkg_proc() { soundContinueAll(); } // 0x451A08 static int gsound_open(const char* fname, int flags, ...) { if ((flags & 2) != 0) { return -1; } File* stream = db_fopen(fname, "rb"); if (stream == NULL) { return -1; } return (int)stream; } // 0x451A1C static long gsound_compressed_tell(int fileHandle) { return -1; } // NOTE: Uncollapsed 0x451A1C. static int gsound_write(int fileHandle, const void* buf, unsigned int size) { return -1; } // 0x451A24 static int gsound_close(int fileHandle) { if (fileHandle == -1) { return -1; } return db_fclose((File*)fileHandle); } // 0x451A30 static int gsound_read(int fileHandle, void* buffer, unsigned int size) { if (fileHandle == -1) { return -1; } return db_fread(buffer, 1, size, (File*)fileHandle); } // 0x451A4C static long gsound_seek(int fileHandle, long offset, int origin) { if (fileHandle == -1) { return -1; } if (db_fseek((File*)fileHandle, offset, origin) != 0) { return -1; } return db_ftell((File*)fileHandle); } // 0x451A70 static long gsound_tell(int handle) { if (handle == -1) { return -1; } return db_ftell((File*)handle); } // 0x451A7C static long gsound_filesize(int handle) { if (handle == -1) { return -1; } return db_filelength((File*)handle); } // 0x451A88 static bool gsound_compressed_query(char* filePath) { return true; } // 0x451A90 static void gsound_internal_speech_callback(void* userData, int a2) { if (a2 == 1) { gsound_speech_tag = NULL; if (gsound_speech_callback_fp) { gsound_speech_callback_fp(); } } } // 0x451AB0 static void gsound_internal_background_callback(void* userData, int a2) { if (a2 == 1) { gsound_background_tag = NULL; if (gsound_background_callback_fp) { gsound_background_callback_fp(); } } } // 0x451AD0 static void gsound_internal_effect_callback(void* userData, int a2) { if (a2 == 1) { --gsound_active_effect_counter; } } // 0x451ADC static int gsound_background_allocate(Sound** soundPtr, int a2, int a3) { int v5 = 10; int v6 = 0; if (a2 == 13) { v6 |= 0x01; } else if (a2 == 14) { v6 |= 0x02; } if (a3 == 15) { v6 |= 0x04; } else if (a3 == 16) { v5 = 42; } Sound* sound = soundAllocate(v6, v5); if (sound == NULL) { return -1; } *soundPtr = sound; return 0; } // gsound_background_find_with_copy // 0x451B30 static int gsound_background_find_with_copy(char* dest, const char* src) { size_t len = strlen(src) + strlen(".ACM"); if (strlen(sound_music_path1) + len > MAX_PATH || strlen(sound_music_path2) + len > MAX_PATH) { if (gsound_debug) { debug_printf("Full background path too long.\n"); } return -1; } if (gsound_debug) { debug_printf(" finding background sound "); } char outPath[MAX_PATH]; sprintf(outPath, "%s%s%s", sound_music_path1, src, ".ACM"); if (gsound_file_exists_f(outPath)) { strncpy(dest, outPath, MAX_PATH); dest[MAX_PATH] = '\0'; return 0; } if (gsound_debug) { debug_printf("by copy "); } gsound_background_remove_last_copy(); char inPath[MAX_PATH]; sprintf(inPath, "%s%s%s", sound_music_path2, src, ".ACM"); FILE* inStream = fopen(inPath, "rb"); if (inStream == NULL) { if (gsound_debug) { debug_printf("Unable to find music file %s to copy down.\n", src); } return -1; } FILE* outStream = fopen(outPath, "wb"); if (outStream == NULL) { if (gsound_debug) { debug_printf("Unable to open music file %s for copying to.", src); } fclose(inStream); return -1; } void* buffer = mem_malloc(0x2000); if (buffer == NULL) { if (gsound_debug) { debug_printf("Out of memory in gsound_background_find_with_copy.\n", src); } fclose(outStream); fclose(inStream); return -1; } bool err = false; while (!feof(inStream)) { size_t bytesRead = fread(buffer, 1, 0x2000, inStream); if (bytesRead == 0) { break; } if (fwrite(buffer, 1, bytesRead, outStream) != bytesRead) { err = true; break; } } mem_free(buffer); fclose(outStream); fclose(inStream); if (err) { if (gsound_debug) { debug_printf("Background sound file copy failed on write -- "); debug_printf("likely out of disc space.\n"); } return -1; } strcpy(background_fname_copied, src); strncpy(dest, outPath, MAX_PATH); dest[MAX_PATH] = '\0'; return 0; } // 0x451E2C static int gsound_background_find_dont_copy(char* dest, const char* src) { char path[MAX_PATH]; int len; len = strlen(src) + strlen(".ACM"); if (strlen(sound_music_path1) + len > MAX_PATH || strlen(sound_music_path2) + len > MAX_PATH) { if (gsound_debug) { debug_printf("Full background path too long.\n"); } return -1; } if (gsound_debug) { debug_printf(" finding background sound "); } sprintf(path, "%s%s%s", sound_music_path1, src, ".ACM"); if (gsound_file_exists_f(path)) { strncpy(dest, path, MAX_PATH); dest[MAX_PATH] = '\0'; return 0; } if (gsound_debug) { debug_printf("in 2nd path "); } sprintf(path, "%s%s%s", sound_music_path2, src, ".ACM"); if (gsound_file_exists_f(path)) { strncpy(dest, path, MAX_PATH); dest[MAX_PATH] = '\0'; return 0; } if (gsound_debug) { debug_printf("-- find failed "); } return -1; } // 0x451F94 static int gsound_speech_find_dont_copy(char* dest, const char* src) { char path[MAX_PATH]; if (strlen(sound_speech_path) + strlen(".acm") > MAX_PATH) { if (gsound_debug) { // FIXME: The message is wrong (notes background path, but here // we're dealing with speech path). debug_printf("Full background path too long.\n"); } return -1; } if (gsound_debug) { debug_printf(" finding speech sound "); } sprintf(path, "%s%s%s", sound_speech_path, src, ".ACM"); // NOTE: Uninline. if (gsound_file_exists_db(path)) { if (gsound_debug) { debug_printf("-- find failed "); } return -1; } strncpy(dest, path, MAX_PATH); dest[MAX_PATH] = '\0'; return 0; } // delete old music file // 0x452088 static void gsound_background_remove_last_copy() { if (background_fname_copied[0] != '\0') { char path[MAX_PATH]; sprintf(path, "%s%s%s", "sound\\music\\", background_fname_copied, ".ACM"); if (remove(path)) { if (gsound_debug) { debug_printf("Deleting old music file failed.\n"); } } background_fname_copied[0] = '\0'; } } // 0x4520EC static int gsound_background_start() { int result; if (gsound_debug) { debug_printf(" playing "); } if (gsound_background_fade) { soundVolume(gsound_background_tag, 1); result = soundFade(gsound_background_tag, 2000, (int)(background_volume * 0.94)); } else { soundVolume(gsound_background_tag, (int)(background_volume * 0.94)); result = soundPlay(gsound_background_tag); } if (result != 0) { if (gsound_debug) { debug_printf("Unable to play background sound.\n"); } result = -1; } return result; } // 0x45219C static int gsound_speech_start() { if (gsound_debug) { debug_printf(" playing "); } soundVolume(gsound_speech_tag, (int)(speech_volume * 0.69)); if (soundPlay(gsound_speech_tag) != 0) { if (gsound_debug) { debug_printf("Unable to play speech sound.\n"); } return -1; } return 0; } // 0x452208 static int gsound_get_music_path(char** out_value, const char* key) { int len; char* copy; char* value; config_get_string(&game_config, GAME_CONFIG_SOUND_KEY, key, out_value); value = *out_value; len = strlen(value); if (value[len - 1] == '\\' || value[len - 1] == '/') { return 0; } copy = (char*)mem_malloc(len + 2); if (copy == NULL) { if (gsound_debug) { debug_printf("Out of memory in gsound_get_music_path.\n"); } return -1; } strcpy(copy, value); copy[len] = '\\'; copy[len + 1] = '\0'; if (config_set_string(&game_config, GAME_CONFIG_SOUND_KEY, key, copy) != 1) { if (gsound_debug) { debug_printf("config_set_string failed in gsound_music_path.\n"); } return -1; } if (config_get_string(&game_config, GAME_CONFIG_SOUND_KEY, key, out_value)) { mem_free(copy); return 0; } if (gsound_debug) { debug_printf("config_get_string failed in gsound_music_path.\n"); } return -1; } // 0x452378 static Sound* gsound_get_sound_ready_for_effect() { int rc; Sound* sound = soundAllocate(5, 10); if (sound == NULL) { if (gsound_debug) { debug_printf(" Can't allocate sound for effect. "); } if (gsound_debug) { debug_printf("soundAllocate returned: %d, %s\n", 0, soundError(0)); } return NULL; } if (sfxc_is_initialized()) { rc = soundSetFileIO(sound, sfxc_cached_open, sfxc_cached_close, sfxc_cached_read, sfxc_cached_write, sfxc_cached_seek, sfxc_cached_tell, sfxc_cached_file_size); } else { rc = soundSetFileIO(sound, audioOpen, audioCloseFile, audioRead, NULL, audioSeek, gsound_compressed_tell, audioFileSize); } if (rc != 0) { if (gsound_debug) { debug_printf("Can't set file IO on sound effect.\n"); } if (gsound_debug) { debug_printf("soundSetFileIO returned: %d, %s\n", rc, soundError(rc)); } soundDelete(sound); return NULL; } rc = soundSetCallback(sound, gsound_internal_effect_callback, NULL); if (rc != 0) { if (gsound_debug) { debug_printf("failed because the callback could not be set.\n"); } if (gsound_debug) { debug_printf("soundSetCallback returned: %d, %s\n", rc, soundError(rc)); } soundDelete(sound); return NULL; } soundVolume(sound, sndfx_volume); return sound; } // Check file for existence. // // 0x4524E0 static bool gsound_file_exists_f(const char* fname) { FILE* f = fopen(fname, "rb"); if (f == NULL) { return false; } fclose(f); return true; } // 0x4524FC static int gsound_file_exists_db(const char* path) { int size; return db_dir_entry(path, &size) == 0; } // 0x452518 static int gsound_setup_paths() { // TODO: Incomplete. return 0; } // 0x452628 int gsound_sfx_q_start() { return gsound_sfx_q_process(0, NULL); } // 0x452634 int gsound_sfx_q_process(Object* a1, void* data) { // 0x518E98 static int lastTime = 0; queue_clear_type(EVENT_TYPE_GSOUND_SFX_EVENT, NULL); AmbientSoundEffectEvent* soundEffectEvent = (AmbientSoundEffectEvent*)data; int ambientSoundEffectIndex = -1; if (soundEffectEvent != NULL) { ambientSoundEffectIndex = soundEffectEvent->ambientSoundEffectIndex; } else { if (wmSfxMaxCount() > 0) { ambientSoundEffectIndex = wmSfxRollNextIdx(); } } AmbientSoundEffectEvent* nextSoundEffectEvent = (AmbientSoundEffectEvent*)mem_malloc(sizeof(*nextSoundEffectEvent)); if (nextSoundEffectEvent == NULL) { return -1; } if (map_data.name[0] == '\0') { return 0; } int delay = 10 * roll_random(15, 20); if (wmSfxMaxCount() > 0) { nextSoundEffectEvent->ambientSoundEffectIndex = wmSfxRollNextIdx(); if (queue_add(delay, NULL, nextSoundEffectEvent, EVENT_TYPE_GSOUND_SFX_EVENT) == -1) { return -1; } } if (isInCombat()) { ambientSoundEffectIndex = -1; } if (ambientSoundEffectIndex != -1) { char* fileName; if (wmSfxIdxName(ambientSoundEffectIndex, &fileName) == 0) { int v7 = get_bk_time(); if (elapsed_tocks(v7, lastTime) >= 5000) { if (gsound_play_sfx_file(fileName) == -1) { debug_printf("\nGsound: playing ambient map sfx: %s. FAILED", fileName); } else { debug_printf("\nGsound: playing ambient map sfx: %s", fileName); } } lastTime = v7; } } return 0; } ================================================ FILE: src/game/gsound.h ================================================ #ifndef FALLOUT_GAME_GSOUND_H_ #define FALLOUT_GAME_GSOUND_H_ #include #include "game/object_types.h" #include "int/sound.h" typedef enum WeaponSoundEffect { WEAPON_SOUND_EFFECT_READY, WEAPON_SOUND_EFFECT_ATTACK, WEAPON_SOUND_EFFECT_OUT_OF_AMMO, WEAPON_SOUND_EFFECT_AMMO_FLYING, WEAPON_SOUND_EFFECT_HIT, WEAPON_SOUND_EFFECT_COUNT, } WeaponSoundEffect; typedef enum SoundEffectActionType { SOUND_EFFECT_ACTION_TYPE_ACTIVE, SOUND_EFFECT_ACTION_TYPE_PASSIVE, } SoundEffectActionType; typedef enum ScenerySoundEffect { SCENERY_SOUND_EFFECT_OPEN, SCENERY_SOUND_EFFECT_CLOSED, SCENERY_SOUND_EFFECT_LOCKED, SCENERY_SOUND_EFFECT_UNLOCKED, SCENERY_SOUND_EFFECT_USED, SCENERY_SOUND_EFFECT_COUNT, } ScenerySoundEffect; typedef enum CharacterSoundEffect { CHARACTER_SOUND_EFFECT_UNUSED, CHARACTER_SOUND_EFFECT_KNOCKDOWN, CHARACTER_SOUND_EFFECT_PASS_OUT, CHARACTER_SOUND_EFFECT_DIE, CHARACTER_SOUND_EFFECT_CONTACT, } CharacterSoundEffect; typedef void(SoundEndCallback)(); int gsound_init(); void gsound_reset(); int gsound_exit(); void gsound_sfx_enable(); void gsound_sfx_disable(); int gsound_sfx_is_enabled(); int gsound_set_master_volume(int value); int gsound_get_master_volume(); int gsound_set_sfx_volume(int value); int gsound_get_sfx_volume(); void gsound_background_disable(); void gsound_background_enable(); int gsound_background_is_enabled(); void gsound_background_volume_set(int value); int gsound_background_volume_get(); int gsound_background_volume_get_set(int a1); void gsound_background_fade_set(int value); int gsound_background_fade_get(); int gsound_background_fade_get_set(int value); void gsound_background_callback_set(SoundEndCallback* callback); SoundEndCallback* gsound_background_callback_get(); SoundEndCallback* gsound_background_callback_get_set(SoundEndCallback* callback); int gsound_background_length_get(); int gsound_background_play(const char* fileName, int a2, int a3, int a4); int gsound_background_play_level_music(const char* a1, int a2); int gsound_background_play_preloaded(); void gsound_background_stop(); void gsound_background_restart_last(int value); void gsound_background_pause(); void gsound_background_unpause(); void gsound_speech_disable(); void gsound_speech_enable(); int gsound_speech_is_enabled(); void gsound_speech_volume_set(int value); int gsound_speech_volume_get(); int gsound_speech_volume_get_set(int volume); void gsound_speech_callback_set(SoundEndCallback* callback); SoundEndCallback* gsound_speech_callback_get(); SoundEndCallback* gsound_speech_callback_get_set(SoundEndCallback* callback); int gsound_speech_length_get(); int gsound_speech_play(const char* fname, int a2, int a3, int a4); int gsound_speech_play_preloaded(); void gsound_speech_stop(); void gsound_speech_pause(); void gsound_speech_unpause(); int gsound_play_sfx_file_volume(const char* a1, int a2); Sound* gsound_load_sound(const char* name, Object* a2); Sound* gsound_load_sound_volume(const char* a1, Object* a2, int a3); void gsound_delete_sfx(Sound* a1); int gsnd_anim_sound(Sound* sound, void* a2); int gsound_play_sound(Sound* a1); int gsound_compute_relative_volume(Object* obj); char* gsnd_build_character_sfx_name(Object* a1, int anim, int extra); char* gsnd_build_ambient_sfx_name(const char* a1); char* gsnd_build_interface_sfx_name(const char* a1); char* gsnd_build_weapon_sfx_name(int effectType, Object* weapon, int hitMode, Object* target); char* gsnd_build_scenery_sfx_name(int actionType, int action, const char* name); char* gsnd_build_open_sfx_name(Object* a1, int a2); void gsound_red_butt_press(int btn, int keyCode); void gsound_red_butt_release(int btn, int keyCode); void gsound_toggle_butt_press(int btn, int keyCode); void gsound_toggle_butt_release(int btn, int keyCode); void gsound_med_butt_press(int btn, int keyCode); void gsound_med_butt_release(int btn, int keyCode); void gsound_lrg_butt_press(int btn, int keyCode); void gsound_lrg_butt_release(int btn, int keyCode); int gsound_play_sfx_file(const char* name); int gsound_sfx_q_start(); int gsound_sfx_q_process(Object* a1, void* a2); #endif /* FALLOUT_GAME_GSOUND_H_ */ ================================================ FILE: src/game/gz.c ================================================ // NOTE: For unknown reason functions in this module use __stdcall instead // of regular __usercall. #include "game/gz.h" #include #include #include #define WIN32_LEAN_AND_MEAN #include // NOTE: Not present in debug symbols in `mapper2.exe`, but can be seen in OS X // binary. // // 0x452740 int gzRealUncompressCopyReal_file(const char* existingFilePath, const char* newFilePath) { FILE* stream = fopen(existingFilePath, "rb"); if (stream == NULL) { return -1; } int magic[2]; magic[0] = fgetc(stream); magic[1] = fgetc(stream); fclose(stream); if (magic[0] == 0x1F && magic[1] == 0x8B) { gzFile inStream = gzopen(existingFilePath, "rb"); FILE* outStream = fopen(newFilePath, "wb"); if (inStream != NULL && outStream != NULL) { for (;;) { int ch = gzgetc(inStream); if (ch == -1) { break; } fputc(ch, outStream); } gzclose(inStream); fclose(outStream); } else { if (inStream != NULL) { gzclose(inStream); } if (outStream != NULL) { fclose(outStream); } return -1; } } else { CopyFileA(existingFilePath, newFilePath, FALSE); } return 0; } // 0x452804 int gzcompress_file(const char* existingFilePath, const char* newFilePath) { FILE* inStream = fopen(existingFilePath, "rb"); if (inStream == NULL) { return -1; } int magic[2]; magic[0] = fgetc(inStream); magic[1] = fgetc(inStream); rewind(inStream); if (magic[0] == 0x1F && magic[1] == 0x8B) { // Source file is already gzipped, there is no need to do anything // besides copying. fclose(inStream); CopyFileA(existingFilePath, newFilePath, FALSE); } else { gzFile outStream = gzopen(newFilePath, "wb"); if (outStream == NULL) { fclose(inStream); return -1; } // Copy byte-by-byte. for (;;) { int ch = fgetc(inStream); if (ch == -1) { break; } gzputc(outStream, ch); } fclose(inStream); gzclose(outStream); } return 0; } // TODO: Check, implementation looks odd. // // 0x4528B8 int gzdecompress_file(const char* existingFilePath, const char* newFilePath) { FILE* stream = fopen(existingFilePath, "rb"); if (stream == NULL) { return -1; } int magic[2]; magic[0] = fgetc(stream); magic[1] = fgetc(stream); fclose(stream); // TODO: Is it broken? if (magic[0] != 0x1F || magic[1] != 0x8B) { gzFile gzstream = gzopen(existingFilePath, "rb"); if (gzstream == NULL) { return -1; } stream = fopen(newFilePath, "wb"); if (stream == NULL) { gzclose(gzstream); return -1; } while (1) { int ch = gzgetc(gzstream); if (ch == -1) { break; } fputc(ch, stream); } gzclose(gzstream); fclose(stream); } else { CopyFileA(existingFilePath, newFilePath, FALSE); } return 0; } ================================================ FILE: src/game/gz.h ================================================ #ifndef FALLOUT_GAME_GZ_H_ #define FALLOUT_GAME_GZ_H_ int gzRealUncompressCopyReal_file(const char* existingFilePath, const char* newFilePath); int gzcompress_file(const char* existingFilePath, const char* newFilePath); int gzdecompress_file(const char* existingFilePath, const char* newFilePath); #endif /* FALLOUT_GAME_GZ_H_ */ ================================================ FILE: src/game/heap.c ================================================ #include "game/heap.h" #include #include #include #include "plib/gnw/debug.h" #include "plib/gnw/memory.h" #define HEAP_BLOCK_HEADER_GUARD (0xDEADC0DE) #define HEAP_BLOCK_FOOTER_GUARD (0xACDCACDC) #define HEAP_BLOCK_HEADER_SIZE (sizeof(HeapBlockHeader)) #define HEAP_BLOCK_FOOTER_SIZE (sizeof(HeapBlockFooter)) #define HEAP_BLOCK_OVERHEAD_SIZE (HEAP_BLOCK_HEADER_SIZE + HEAP_BLOCK_FOOTER_SIZE) // The initial length of [handles] array within [Heap]. #define HEAP_HANDLES_INITIAL_LENGTH (64) // The initial length of [heap_free_list] array. #define HEAP_FREE_BLOCKS_INITIAL_LENGTH (128) // The initial length of [heap_moveable_list] array. #define HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH (64) // The initial length of [heap_subblock_list] array. #define HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH (64) // The initial length of [heap_fake_move_list] array. #define HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH (64) // The minimum size of block for splitting. #define HEAP_BLOCK_MIN_SIZE (128 + HEAP_BLOCK_OVERHEAD_SIZE) #define HEAP_HANDLE_STATE_INVALID (-1) // The only allowed combination is LOCKED | SYSTEM. typedef enum HeapBlockState { HEAP_BLOCK_STATE_FREE = 0x00, HEAP_BLOCK_STATE_MOVABLE = 0x01, HEAP_BLOCK_STATE_LOCKED = 0x02, HEAP_BLOCK_STATE_SYSTEM = 0x04, } HeapBlockState; typedef struct HeapBlockHeader { int guard; int size; unsigned int state; int handle_index; } HeapBlockHeader; typedef struct HeapBlockFooter { int guard; } HeapBlockFooter; typedef struct HeapMoveableExtent { // Pointer to the first block in the extent. unsigned char* data; // Total number of free or moveable blocks in the extent. int blocksLength; // Number of moveable blocks in the extent. int moveableBlocksLength; // Total data size of blocks in the extent. This value does not include // the size of blocks overhead. int size; } HeapMoveableExtent; static_assert(sizeof(HeapBlockHeader) == 16, "wrong size"); static_assert(sizeof(HeapBlockFooter) == 4, "wrong size"); static_assert(sizeof(HeapMoveableExtent) == 16, "wrong size"); static_assert(sizeof(HeapHandle) == 8, "wrong size"); static_assert(sizeof(Heap) == 48, "wrong size"); static bool heap_create_lists(); static void heap_destroy_lists(); static bool heap_init_handles(Heap* heap); static bool heap_exit_handles(Heap* heap); static bool heap_acquire_handle(Heap* heap, int* handleIndexPtr); static bool heap_release_handle(Heap* heap, int handleIndex); static bool heap_clear_handles(Heap* heap, HeapHandle* handles, unsigned int count); static bool heap_find_free_block(Heap* heap, int size, void** blockPtr, int a4); static bool heap_build_free_list(Heap* heap); static bool heap_sort_free_list(Heap* heap); static int heap_qsort_compare_free(const void* a1, const void* a2); static bool heap_build_moveable_list(Heap* heap, int* moveableExtentsLengthPtr, int* maxBlocksLengthPtr); static bool heap_sort_moveable_list(Heap* heap, size_t count); static int heap_qsort_compare_moveable(const void* a1, const void* a2); static bool heap_build_subblock_list(int extentIndex); static bool heap_sort_subblock_list(size_t count); static int heap_qsort_compare_subblock(const void* a1, const void* a2); static bool heap_build_fake_move_list(size_t count); // An array of pointers to free heap blocks. // // 0x518E9C static unsigned char** heap_free_list = NULL; // An array of moveable extents in heap. // // 0x518EA0 static HeapMoveableExtent* heap_moveable_list = NULL; // An array of pointers to moveable heap blocks. // // 0x518EA4 static unsigned char** heap_subblock_list = NULL; // An array of indexes into [heap_free_list] array to track which free blocks // were already reserved for subsequent moving. // // 0x518EA8 static int* heap_fake_move_list = NULL; // The length of the [heap_free_list] array. // // 0x518EAC static int heap_free_list_size = 0; // The length of [heap_moveable_list] array. // // 0x518EB0 static int heap_moveable_list_size = 0; // The length of [heap_subblock_list] array. // // 0x518EB4 static int heap_subblock_list_size = 0; // The length of [heap_fake_move_list] array. // // 0x518EB8 static size_t heap_fake_move_list_size = 0; // The number of heaps. // // This value is used to init/free internal temporary buffers // needed for any heap. // // 0x518EBC static int heap_count = 0; // 0x452974 bool heap_init(Heap* heap, int a2) { if (heap == NULL) { return false; } if (heap_count == 0) { if (!heap_create_lists()) { return false; } } memset(heap, 0, sizeof(*heap)); if (heap_init_handles(heap)) { int size = (a2 >> 10) + a2; heap->data = (unsigned char*)mem_malloc(size); if (heap->data != NULL) { heap->size = size; heap->freeBlocks = 1; heap->freeSize = heap->size - HEAP_BLOCK_OVERHEAD_SIZE; HeapBlockHeader* blockHeader = (HeapBlockHeader*)heap->data; blockHeader->guard = HEAP_BLOCK_HEADER_GUARD; blockHeader->size = heap->freeSize; blockHeader->state = 0; blockHeader->handle_index = -1; HeapBlockFooter* blockFooter = (HeapBlockFooter*)(heap->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; heap_count++; return true; } } if (heap_count == 0) { heap_destroy_lists(); } return false; } // 0x452A3C bool heap_exit(Heap* heap) { if (heap == NULL) { return false; } for (int index = 0; index < heap->handlesLength; index++) { HeapHandle* handle = &(heap->handles[index]); if (handle->state == 4 && handle->data != NULL) { mem_free(handle->data); } } // NOTE: Uninline. heap_exit_handles(heap); if (heap->data != NULL) { mem_free(heap->data); } memset(heap, 0, sizeof(*heap)); heap_count--; if (heap_count == 0) { heap_destroy_lists(); } return true; } // 0x452AD0 bool heap_allocate(Heap* heap, int* handleIndexPtr, int size, int a4) { HeapBlockHeader* blockHeader; int state; int blockSize; HeapHandle* handle; if (heap == NULL || handleIndexPtr == NULL || size == 0) { goto err; } if (a4 != 0 && a4 != 1) { a4 = 0; } void* block; if (!heap_find_free_block(heap, size, &block, a4)) { goto err; } blockHeader = (HeapBlockHeader*)block; state = blockHeader->state; int handleIndex; if (!heap_acquire_handle(heap, &handleIndex)) { goto err_no_handle; } blockSize = blockHeader->size; handle = &(heap->handles[handleIndex]); if (state == HEAP_BLOCK_STATE_SYSTEM) { // Bind block to handle. blockHeader->handle_index = handleIndex; // Bind handle to block and mark it as system handle->state = HEAP_BLOCK_STATE_SYSTEM; handle->data = (unsigned char*)block; // Update heap stats heap->systemBlocks++; heap->systemSize += size; *handleIndexPtr = handleIndex; return true; } if (state == HEAP_BLOCK_STATE_FREE) { int remainingSize = blockSize - size; if (remainingSize > HEAP_BLOCK_MIN_SIZE) { // The block we've just found is big enough for splitting, first // resize it to take just what was requested. blockHeader->size = size; blockSize = size; // HeapBlockFooter* blockFooter = (HeapBlockFooter*)((unsigned char*)block + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; // Obtain beginning of the next block. unsigned char* nextBlock = (unsigned char*)block + blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; // Setup next block's header... HeapBlockHeader* nextBlockHeader = (HeapBlockHeader*)nextBlock; nextBlockHeader->guard = HEAP_BLOCK_HEADER_GUARD; nextBlockHeader->size = remainingSize - HEAP_BLOCK_OVERHEAD_SIZE; nextBlockHeader->state = HEAP_BLOCK_STATE_FREE; nextBlockHeader->handle_index = -1; // ... and footer. HeapBlockFooter* nextBlockFooter = (HeapBlockFooter*)(nextBlock + nextBlockHeader->size + HEAP_BLOCK_HEADER_SIZE); nextBlockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; // Update heap stats heap->freeBlocks++; heap->freeSize -= HEAP_BLOCK_OVERHEAD_SIZE; } // Bind block to handle and mark it as moveable blockHeader->state = HEAP_BLOCK_STATE_MOVABLE; blockHeader->handle_index = handleIndex; // Bind handle to block and mark it as moveable handle->state = HEAP_BLOCK_STATE_MOVABLE; handle->data = (unsigned char*)block; // Update heap stats heap->freeBlocks--; heap->moveableBlocks++; heap->freeSize -= blockSize; heap->moveableSize += blockSize; *handleIndexPtr = handleIndex; return true; } // NOTE: Uninline. heap_release_handle(heap, handleIndex); debug_printf("Heap Error: Unknown block state during allocation.\n"); err_no_handle: debug_printf("Heap Error: Could not acquire handle for new block.\n"); if (state == HEAP_BLOCK_STATE_SYSTEM) { mem_free(block); } err: debug_printf("Heap Warning: Could not allocate block of %d bytes.\n", size); return false; } // 0x452CB4 bool heap_deallocate(Heap* heap, int* handleIndexPtr) { if (heap == NULL || handleIndexPtr == NULL) { debug_printf("Heap Error: Could not deallocate block.\n"); return false; } int handleIndex = *handleIndexPtr; HeapHandle* handle = &(heap->handles[handleIndex]); HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data; if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) { debug_printf("Heap Error: Bad guard begin detected during deallocate.\n"); } HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) { debug_printf("Heap Error: Bad guard end detected during deallocate.\n"); } if (handle->state != blockHeader->state) { debug_printf("Heap Error: Mismatched block states detected during deallocate.\n"); } if ((handle->state & HEAP_BLOCK_STATE_LOCKED) != 0) { debug_printf("Heap Error: Attempt to deallocate locked block.\n"); return false; } int size = blockHeader->size; if (handle->state == HEAP_BLOCK_STATE_MOVABLE) { // Unbind block from handle and mark it as free. blockHeader->handle_index = -1; blockHeader->state = HEAP_BLOCK_STATE_FREE; // Update heap stats heap->freeBlocks++; heap->moveableBlocks--; heap->freeSize += size; heap->moveableSize -= size; // NOTE: Uninline. heap_release_handle(heap, handleIndex); return true; } if (handle->state == HEAP_BLOCK_STATE_SYSTEM) { // Release system memory mem_free(handle->data); // Update heap stats heap->systemBlocks--; heap->systemSize -= size; // NOTE: Uninline. heap_release_handle(heap, handleIndex); return true; } debug_printf("Heap Error: Unknown block state during deallocation.\n"); return false; } // 0x452DE0 bool heap_lock(Heap* heap, int handleIndex, unsigned char** bufferPtr) { if (heap == NULL) { debug_printf("Heap Error: Could not lock block"); return false; } HeapHandle* handle = &(heap->handles[handleIndex]); HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data; if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) { debug_printf("Heap Error: Bad guard begin detected during lock.\n"); return false; } HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) { debug_printf("Heap Error: Bad guard end detected during lock.\n"); return false; } if (handle->state != blockHeader->state) { debug_printf("Heap Error: Mismatched block states detected during lock.\n"); return false; } if ((handle->state & HEAP_BLOCK_STATE_LOCKED) != 0) { debug_printf("Heap Error: Attempt to lock a previously locked block."); return false; } if (handle->state == HEAP_BLOCK_STATE_MOVABLE) { blockHeader->state = HEAP_BLOCK_STATE_LOCKED; handle->state = HEAP_BLOCK_STATE_LOCKED; heap->moveableBlocks--; heap->lockedBlocks++; int size = blockHeader->size; heap->moveableSize -= size; heap->lockedSize += size; *bufferPtr = handle->data + HEAP_BLOCK_HEADER_SIZE; return true; } if (handle->state == HEAP_BLOCK_STATE_SYSTEM) { blockHeader->state |= HEAP_BLOCK_STATE_LOCKED; handle->state |= HEAP_BLOCK_STATE_LOCKED; *bufferPtr = handle->data + HEAP_BLOCK_HEADER_SIZE; return true; } debug_printf("Heap Error: Unknown block state during lock.\n"); return false; } // 0x452EE4 bool heap_unlock(Heap* heap, int handleIndex) { if (heap == NULL) { debug_printf("Heap Error: Could not unlock block.\n"); return false; } HeapHandle* handle = &(heap->handles[handleIndex]); HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data; if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) { debug_printf("Heap Error: Bad guard begin detected during unlock.\n"); } HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) { debug_printf("Heap Error: Bad guard end detected during unlock.\n"); } if (handle->state != blockHeader->state) { debug_printf("Heap Error: Mismatched block states detected during unlock.\n"); } if ((handle->state & HEAP_BLOCK_STATE_LOCKED) == 0) { debug_printf("Heap Error: Attempt to unlock a previously unlocked block.\n"); debug_printf("Heap Error: Could not unlock block.\n"); return false; } if ((handle->state & HEAP_BLOCK_STATE_SYSTEM) != 0) { blockHeader->state = HEAP_BLOCK_STATE_SYSTEM; handle->state = HEAP_BLOCK_STATE_SYSTEM; return true; } blockHeader->state = HEAP_BLOCK_STATE_MOVABLE; handle->state = HEAP_BLOCK_STATE_MOVABLE; heap->moveableBlocks++; heap->lockedBlocks--; int size = blockHeader->size; heap->moveableSize += size; heap->lockedSize -= size; return true; } // 0x452FC4 bool heap_validate(Heap* heap) { debug_printf("Validating heap...\n"); int blocksCount = heap->freeBlocks + heap->moveableBlocks + heap->lockedBlocks; unsigned char* ptr = heap->data; int freeBlocks = 0; int freeSize = 0; int moveableBlocks = 0; int moveableSize = 0; int lockedBlocks = 0; int lockedSize = 0; for (int index = 0; index < blocksCount; index++) { HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr; if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) { debug_printf("Bad guard begin detected during validate.\n"); return false; } HeapBlockFooter* blockFooter = (HeapBlockFooter*)(ptr + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) { debug_printf("Bad guard end detected during validate.\n"); return false; } if (blockHeader->state == HEAP_BLOCK_STATE_FREE) { freeBlocks++; freeSize += blockHeader->size; } else if (blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) { moveableBlocks++; moveableSize += blockHeader->size; } else if (blockHeader->state == HEAP_BLOCK_STATE_LOCKED) { lockedBlocks++; lockedSize += blockHeader->size; } if (index != blocksCount - 1) { ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; if (ptr > (heap->data + heap->size)) { debug_printf("Ran off end of heap during validate!\n"); return false; } } } if (freeBlocks != heap->freeBlocks) { debug_printf("Invalid number of free blocks.\n"); return false; } if (freeSize != heap->freeSize) { debug_printf("Invalid size of free blocks.\n"); return false; } if (moveableBlocks != heap->moveableBlocks) { debug_printf("Invalid number of moveable blocks.\n"); return false; } if (moveableSize != heap->moveableSize) { debug_printf("Invalid size of moveable blocks.\n"); return false; } if (lockedBlocks != heap->lockedBlocks) { debug_printf("Invalid number of locked blocks.\n"); return false; } if (lockedSize != heap->lockedSize) { debug_printf("Invalid size of locked blocks.\n"); return false; } debug_printf("Heap is O.K.\n"); int systemBlocks = 0; int systemSize = 0; for (int handleIndex = 0; handleIndex < heap->handlesLength; handleIndex++) { HeapHandle* handle = &(heap->handles[handleIndex]); if (handle->state != HEAP_HANDLE_STATE_INVALID && (handle->state & HEAP_BLOCK_STATE_SYSTEM) != 0) { HeapBlockHeader* blockHeader = (HeapBlockHeader*)handle->data; if (blockHeader->guard != HEAP_BLOCK_HEADER_GUARD) { debug_printf("Bad guard begin detected in system block during validate.\n"); return false; } HeapBlockFooter* blockFooter = (HeapBlockFooter*)(handle->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); if (blockFooter->guard != HEAP_BLOCK_FOOTER_GUARD) { debug_printf("Bad guard end detected in system block during validate.\n"); return false; } systemBlocks++; systemSize += blockHeader->size; } } if (systemBlocks != heap->systemBlocks) { debug_printf("Invalid number of system blocks.\n"); return false; } if (systemSize != heap->systemSize) { debug_printf("Invalid size of system blocks.\n"); return false; } return true; } // 0x4532AC bool heap_stats(Heap* heap, char* dest) { if (heap == NULL || dest == NULL) { return false; } const char* format = "[Heap]\n" "Total free blocks: %d\n" "Total free size: %d\n" "Total moveable blocks: %d\n" "Total moveable size: %d\n" "Total locked blocks: %d\n" "Total locked size: %d\n" "Total system blocks: %d\n" "Total system size: %d\n" "Total handles: %d\n" "Total heaps: %d"; sprintf(dest, format, heap->freeBlocks, heap->freeSize, heap->moveableBlocks, heap->moveableSize, heap->lockedBlocks, heap->lockedSize, heap->systemBlocks, heap->systemSize, heap->handlesLength, heap_count); return true; } // 0x453304 static bool heap_create_lists() { // NOTE: Original code is slightly different. It uses deep nesting or a // bunch of goto's to free alloc'ed buffers one by one starting from where // it has failed. do { heap_free_list = (unsigned char**)mem_malloc(sizeof(*heap_free_list) * HEAP_FREE_BLOCKS_INITIAL_LENGTH); if (heap_free_list == NULL) { break; } heap_free_list_size = HEAP_FREE_BLOCKS_INITIAL_LENGTH; heap_moveable_list = (HeapMoveableExtent*)mem_malloc(sizeof(*heap_moveable_list) * HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH); if (heap_moveable_list == NULL) { break; } heap_moveable_list_size = HEAP_MOVEABLE_EXTENTS_INITIAL_LENGTH; heap_subblock_list = (unsigned char**)mem_malloc(sizeof(*heap_subblock_list) * HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH); if (heap_subblock_list == NULL) { break; } heap_subblock_list_size = HEAP_MOVEABLE_BLOCKS_INITIAL_LENGTH; heap_fake_move_list = (int*)mem_malloc(sizeof(*heap_fake_move_list) * HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH); if (heap_fake_move_list == NULL) { break; } heap_fake_move_list_size = HEAP_RESERVED_FREE_BLOCK_INDEXES_INITIAL_LENGTH; return true; } while (0); // NOTE: Original code frees them one by one without calling this function. heap_destroy_lists(); return false; } // 0x4533A0 static void heap_destroy_lists() { if (heap_fake_move_list != NULL) { mem_free(heap_fake_move_list); heap_fake_move_list = NULL; } heap_fake_move_list_size = 0; if (heap_subblock_list != NULL) { mem_free(heap_subblock_list); heap_subblock_list = NULL; } heap_subblock_list_size = 0; if (heap_moveable_list != NULL) { mem_free(heap_moveable_list); heap_moveable_list = NULL; } heap_moveable_list_size = 0; if (heap_free_list != NULL) { mem_free(heap_free_list); heap_free_list = NULL; } heap_free_list_size = 0; } // 0x453430 static bool heap_init_handles(Heap* heap) { heap->handles = (HeapHandle*)mem_malloc(sizeof(*heap->handles) * HEAP_HANDLES_INITIAL_LENGTH); if (heap->handles != NULL) { // NOTE: Uninline. if (heap_clear_handles(heap, heap->handles, HEAP_HANDLES_INITIAL_LENGTH) == true) { heap->handlesLength = HEAP_HANDLES_INITIAL_LENGTH; return true; } debug_printf("Heap Error: Could not allocate handles.\n"); return false; } debug_printf("Heap Error : Could not initialize handles.\n"); return false; } // NOTE: Inlined. // // 0x453480 static bool heap_exit_handles(Heap* heap) { if (heap->handles == NULL) { return false; } mem_free(heap->handles); heap->handles = NULL; heap->handlesLength = 0; return true; } // 0x4534B0 static bool heap_acquire_handle(Heap* heap, int* handleIndexPtr) { // Loop thru already available handles and find first that is not currently // used. for (int index = 0; index < heap->handlesLength; index++) { HeapHandle* handle = &(heap->handles[index]); if (handle->state == HEAP_HANDLE_STATE_INVALID) { *handleIndexPtr = index; return true; } } // If we're here the search above failed, we have to allocate more handles. HeapHandle* handles = (HeapHandle*)mem_realloc(heap->handles, sizeof(*handles) * (heap->handlesLength + HEAP_HANDLES_INITIAL_LENGTH)); if (handles == NULL) { return false; } heap->handles = handles; // NOTE: Uninline. heap_clear_handles(heap, &(heap->handles[heap->handlesLength]), HEAP_HANDLES_INITIAL_LENGTH); *handleIndexPtr = heap->handlesLength; heap->handlesLength += HEAP_HANDLES_INITIAL_LENGTH; return true; } // NOTE: Inlined. // // 0x453538 static bool heap_release_handle(Heap* heap, int handleIndex) { heap->handles[handleIndex].state = HEAP_HANDLE_STATE_INVALID; heap->handles[handleIndex].data = NULL; return true; } // NOTE: Inlined. // // 0x453558 static bool heap_clear_handles(Heap* heap, HeapHandle* handles, unsigned int count) { unsigned int index; for (index = 0; index < count; index++) { handles[index].state = HEAP_HANDLE_STATE_INVALID; handles[index].data = NULL; } return true; } // 0x453588 static bool heap_find_free_block(Heap* heap, int size, void** blockPtr, int a4) { unsigned char* biggestFreeBlock; HeapBlockHeader* biggestFreeBlockHeader; int biggestFreeBlockSize; HeapMoveableExtent* extent; int reservedFreeBlockIndex; HeapBlockHeader* blockHeader; HeapBlockFooter* blockFooter; if (!heap_build_free_list(heap)) { goto system; } if (size > heap->freeSize) { goto system; } // NOTE: Uninline. heap_sort_free_list(heap); // Take last free block (the biggest one). biggestFreeBlock = heap_free_list[heap->freeBlocks - 1]; biggestFreeBlockHeader = (HeapBlockHeader*)biggestFreeBlock; biggestFreeBlockSize = biggestFreeBlockHeader->size; // Make sure it can encompass new block of given size. if (biggestFreeBlockSize >= size) { // Now loop thru all free blocks and find the first one that's at least // as large as what was required. int index; for (index = 0; index < heap->freeBlocks; index++) { unsigned char* block = heap_free_list[index]; HeapBlockHeader* blockHeader = (HeapBlockHeader*)block; if (blockHeader->size >= size) { break; } } *blockPtr = heap_free_list[index]; return true; } int moveableExtentsCount; int maxBlocksCount; if (!heap_build_moveable_list(heap, &moveableExtentsCount, &maxBlocksCount)) { goto system; } // Ensure the length of [heap_fake_move_list] array is big enough // to index all blocks for longest moveable extent. // NOTE: Uninline. if (!heap_build_fake_move_list(maxBlocksCount)) { goto system; } // NOTE: Uninline. heap_sort_moveable_list(heap, moveableExtentsCount); if (moveableExtentsCount == 0) { goto system; } // Loop thru moveable extents and find first one which is big enough for new // block and for which we can move every block somewhere. int extentIndex; for (extentIndex = 0; extentIndex < moveableExtentsCount; extentIndex++) { HeapMoveableExtent* extent = &(heap_moveable_list[extentIndex]); // Calculate extent size including the size of the overhead. Exclude the // size of one overhead for current block. int extentSize = extent->size + HEAP_BLOCK_OVERHEAD_SIZE * extent->blocksLength - HEAP_BLOCK_OVERHEAD_SIZE; // Make sure current extent is worth moving which means there will be // enough size for new block of given size after moving current extent. if (extentSize < size) { continue; } if (!heap_build_subblock_list(extentIndex)) { continue; } // Sort moveable blocks by size (smallest -> largest) // NOTE: Uninline. heap_sort_subblock_list(extent->moveableBlocksLength); int reservedBlocksLength = 0; // Loop thru sorted moveable blocks and build array of reservations. for (int moveableBlockIndex = 0; moveableBlockIndex < extent->moveableBlocksLength; moveableBlockIndex++) { // Grab current moveable block. unsigned char* moveableBlock = heap_subblock_list[moveableBlockIndex]; HeapBlockHeader* moveableBlockHeader = (HeapBlockHeader*)moveableBlock; // Make sure there is at least one free block that's big enough // to encompass it. if (biggestFreeBlockSize < moveableBlockHeader->size) { continue; } // Loop thru sorted free blocks (smallest -> largest) and find // first unreserved free block that can encompass current moveable // block. int freeBlockIndex; for (freeBlockIndex = 0; freeBlockIndex < heap->freeBlocks; freeBlockIndex++) { // Grab current free block. unsigned char* freeBlock = heap_free_list[freeBlockIndex]; HeapBlockHeader* freeBlockHeader = (HeapBlockHeader*)freeBlock; // Make sure it's size is enough for current moveable block. if (freeBlockHeader->size < moveableBlockHeader->size) { continue; } // Make sure it's outside of the current extent, because free // blocks inside it is already taken into account in // `extentSize`. if (freeBlock >= extent->data && freeBlock < extent->data + extentSize + HEAP_BLOCK_OVERHEAD_SIZE) { continue; } // Loop thru reserved free blocks to make to make sure we // can take it. int freeBlocksIndexesIndex; for (freeBlocksIndexesIndex = 0; freeBlocksIndexesIndex < reservedBlocksLength; freeBlocksIndexesIndex++) { if (freeBlockIndex == heap_fake_move_list[freeBlocksIndexesIndex]) { // This free block was already reserved, there is no // need to continue. break; } } if (freeBlocksIndexesIndex == reservedBlocksLength) { // We've looked thru entire reserved free blocks array // and haven't found resevation. That means we can // reseve current free block, so stop further search. break; } } if (freeBlockIndex == heap->freeBlocks) { // We've looked thru entire free blocks array and haven't // found suitable free block for current moveable block. // Skip the rest of the search, since we want to move the // entire extent and just found out that at least one block // cannot be moved. break; } // If we get this far, we've found suitable free block for // current moveable block, save it for later usage. heap_fake_move_list[reservedBlocksLength++] = freeBlockIndex; } if (reservedBlocksLength == extent->moveableBlocksLength) { // We've reserved free block for every movable block in current // extent. break; } } if (extentIndex == moveableExtentsCount) { // We've looked thru entire moveable extents and haven't found one // suitable for moving. goto system; } extent = &(heap_moveable_list[extentIndex]); reservedFreeBlockIndex = 0; for (int moveableBlockIndex = 0; moveableBlockIndex < extent->moveableBlocksLength; moveableBlockIndex++) { unsigned char* moveableBlock = heap_subblock_list[moveableBlockIndex]; HeapBlockHeader* moveableBlockHeader = (HeapBlockHeader*)moveableBlock; int moveableBlockSize = moveableBlockHeader->size; if (biggestFreeBlockSize < moveableBlockSize) { continue; } unsigned char* freeBlock = heap_free_list[heap_fake_move_list[reservedFreeBlockIndex++]]; HeapBlockHeader* freeBlockHeader = (HeapBlockHeader*)freeBlock; int freeBlockSize = freeBlockHeader->size; memcpy(freeBlock, moveableBlock, moveableBlockSize + HEAP_BLOCK_OVERHEAD_SIZE); heap->handles[freeBlockHeader->handle_index].data = freeBlock; // Calculate remaining size of the free block after moving. int remainingSize = freeBlockSize - moveableBlockSize; if (remainingSize != 0) { if (remainingSize < HEAP_BLOCK_MIN_SIZE) { // The remaining size of the former free block is too small to // become a new free block, merge it into the current one. freeBlockHeader->size += remainingSize; HeapBlockFooter* freeBlockFooter = (HeapBlockFooter*)(freeBlock + freeBlockHeader->size + HEAP_BLOCK_HEADER_SIZE); freeBlockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; // The remaining size of the free block was merged into moveable // block, update heap stats accordingly. heap->freeSize -= remainingSize; heap->moveableSize += remainingSize; } else { // The remaining size is enough for a new block. The current // block is already properly formatted - it's header and // footer was copied from moveable block. Since this was a valid // free block it also has it's footer already in place. So the // only thing left is header. unsigned char* nextFreeBlock = freeBlock + freeBlockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; HeapBlockHeader* nextFreeBlockHeader = (HeapBlockHeader*)nextFreeBlock; nextFreeBlockHeader->state = HEAP_BLOCK_STATE_FREE; nextFreeBlockHeader->handle_index = -1; nextFreeBlockHeader->size = remainingSize - HEAP_BLOCK_OVERHEAD_SIZE; nextFreeBlockHeader->guard = HEAP_BLOCK_HEADER_GUARD; heap->freeBlocks++; heap->freeSize -= HEAP_BLOCK_OVERHEAD_SIZE; } } } heap->freeBlocks -= extent->blocksLength - 1; heap->freeSize += (extent->blocksLength - 1) * HEAP_BLOCK_OVERHEAD_SIZE; // Create one free block from entire moveable extent. blockHeader = (HeapBlockHeader*)extent->data; blockHeader->guard = HEAP_BLOCK_HEADER_GUARD; blockHeader->size = extent->size + (extent->blocksLength - 1) * HEAP_BLOCK_OVERHEAD_SIZE; blockHeader->state = HEAP_BLOCK_STATE_FREE; blockHeader->handle_index = -1; blockFooter = (HeapBlockFooter*)(extent->data + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; *blockPtr = extent->data; return true; system: if (1) { char stats[512]; if (heap_stats(heap, stats)) { debug_printf("\n%s\n", stats); } if (a4 == 0) { debug_printf("Allocating block from system memory...\n"); unsigned char* block = (unsigned char*)mem_malloc(size + HEAP_BLOCK_OVERHEAD_SIZE); if (block == NULL) { debug_printf("fatal error: internal_malloc() failed in heap_find_free_block()!\n"); return false; } HeapBlockHeader* blockHeader = (HeapBlockHeader*)block; blockHeader->guard = HEAP_BLOCK_HEADER_GUARD; blockHeader->size = size; blockHeader->state = HEAP_BLOCK_STATE_SYSTEM; blockHeader->handle_index = -1; HeapBlockFooter* blockFooter = (HeapBlockFooter*)(block + blockHeader->size + HEAP_BLOCK_HEADER_SIZE); blockFooter->guard = HEAP_BLOCK_FOOTER_GUARD; *blockPtr = block; return true; } } return false; } // 0x453BC4 static bool heap_build_free_list(Heap* heap) { if (heap->freeBlocks == 0) { return false; } if (heap->freeBlocks > heap_free_list_size) { unsigned char** freeBlocks = (unsigned char**)mem_realloc(heap_free_list, sizeof(*freeBlocks) * heap->freeBlocks); if (freeBlocks == NULL) { return false; } heap_free_list = (unsigned char**)freeBlocks; heap_free_list_size = heap->freeBlocks; } int blocksLength = heap->moveableBlocks + heap->freeBlocks + heap->lockedBlocks; unsigned char* ptr = heap->data; int freeBlockIndex = 0; while (blocksLength != 0) { if (freeBlockIndex >= heap->freeBlocks) { break; } HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr; if (blockHeader->state == HEAP_BLOCK_STATE_FREE) { // Join consecutive free blocks if any. while (blocksLength > 1) { // Grab next block and check if's a free block. HeapBlockHeader* nextBlockHeader = (HeapBlockHeader*)(ptr + blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE); if (nextBlockHeader->state != HEAP_BLOCK_STATE_FREE) { break; } // Accumulate it's size plus size of the overhead in the main // block. blockHeader->size += nextBlockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; // Update heap stats, the free size increased because we've just // remove overhead for one block. heap->freeBlocks--; heap->freeSize += HEAP_BLOCK_OVERHEAD_SIZE; blocksLength--; } heap_free_list[freeBlockIndex++] = ptr; } // Move pointer to the header of the next block. ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; blocksLength--; } return true; } // NOTE: Inlined. // // 0x453C9C static bool heap_sort_free_list(Heap* heap) { if (heap->freeBlocks > 1) { qsort(heap_free_list, heap->freeBlocks, sizeof(*heap_free_list), heap_qsort_compare_free); } return true; } // 0x453CC4 static int heap_qsort_compare_free(const void* a1, const void* a2) { HeapBlockHeader* header1 = *(HeapBlockHeader**)a1; HeapBlockHeader* header2 = *(HeapBlockHeader**)a2; return header1->size - header2->size; } // 0x453CD0 static bool heap_build_moveable_list(Heap* heap, int* moveableExtentsLengthPtr, int* maxBlocksLengthPtr) { // Calculate max number of extents. It's only possible when every // free or moveable block is followed by locked block. int maxExtentsCount = heap->moveableBlocks + heap->freeBlocks; if (maxExtentsCount <= 2) { debug_printf("<[couldn't build moveable list]>\n"); return false; } if (maxExtentsCount > heap_moveable_list_size) { HeapMoveableExtent* moveableExtents = (HeapMoveableExtent*)mem_realloc(heap_moveable_list, sizeof(*heap_moveable_list) * maxExtentsCount); if (moveableExtents == NULL) { return false; } heap_moveable_list = moveableExtents; heap_moveable_list_size = maxExtentsCount; } unsigned char* ptr = heap->data; int blocksLength = heap->moveableBlocks + heap->freeBlocks + heap->lockedBlocks; int maxBlocksLength = 0; int extentIndex = 0; while (blocksLength != 0) { if (extentIndex >= maxExtentsCount) { break; } HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr; if (blockHeader->state == HEAP_BLOCK_STATE_FREE || blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) { HeapMoveableExtent* extent = &(heap_moveable_list[extentIndex++]); extent->data = ptr; extent->blocksLength = 1; extent->moveableBlocksLength = 0; extent->size = blockHeader->size; if (blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) { extent->moveableBlocksLength = 1; } // Calculate moveable extent stats from consecutive blocks. while (blocksLength > 1) { // Grab next block and check if's a free or moveable block. HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr; HeapBlockHeader* nextBlockHeader = (HeapBlockHeader*)(ptr + blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE); if (nextBlockHeader->state != HEAP_BLOCK_STATE_FREE && nextBlockHeader->state != HEAP_BLOCK_STATE_MOVABLE) { break; } // Update extent stats. extent->blocksLength++; extent->size += nextBlockHeader->size; if (nextBlockHeader->state == HEAP_BLOCK_STATE_MOVABLE) { extent->moveableBlocksLength++; } // Move pointer to the beginning of the next block. ptr += (blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE); blocksLength--; } if (extent->blocksLength > maxBlocksLength) { maxBlocksLength = extent->blocksLength; } } // ptr might have been advanced during the loop above. blockHeader = (HeapBlockHeader*)ptr; ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; blocksLength--; }; *moveableExtentsLengthPtr = extentIndex; *maxBlocksLengthPtr = maxBlocksLength; return true; } // NOTE: Inlined. // // 0x453E54 static bool heap_sort_moveable_list(Heap* heap, size_t count) { qsort(heap_moveable_list, count, sizeof(*heap_moveable_list), heap_qsort_compare_moveable); return true; } // 0x453E74 static int heap_qsort_compare_moveable(const void* a1, const void* a2) { HeapMoveableExtent* v1 = (HeapMoveableExtent*)a1; HeapMoveableExtent* v2 = (HeapMoveableExtent*)a2; return v1->size - v2->size; } // Build list of pointers to moveable blocks in given extent. // // 0x453E80 static bool heap_build_subblock_list(int extentIndex) { HeapMoveableExtent* extent = &(heap_moveable_list[extentIndex]); if (extent->moveableBlocksLength > heap_subblock_list_size) { unsigned char** moveableBlocks = (unsigned char**)mem_realloc(heap_subblock_list, sizeof(*heap_subblock_list) * extent->moveableBlocksLength); if (moveableBlocks == NULL) { return false; } heap_subblock_list = moveableBlocks; heap_subblock_list_size = extent->moveableBlocksLength; } unsigned char* ptr = extent->data; int moveableBlockIndex = 0; for (int index = 0; index < extent->blocksLength; index++) { HeapBlockHeader* blockHeader = (HeapBlockHeader*)ptr; if (blockHeader->state == HEAP_BLOCK_STATE_MOVABLE) { heap_subblock_list[moveableBlockIndex++] = ptr; } ptr += blockHeader->size + HEAP_BLOCK_OVERHEAD_SIZE; } return moveableBlockIndex == extent->moveableBlocksLength; } // NOTE: Inlined. // // 0x453F24 static bool heap_sort_subblock_list(size_t count) { qsort(heap_subblock_list, count, sizeof(*heap_subblock_list), heap_qsort_compare_subblock); return true; } // NOTE: Uncollapsed 0x453CC4. static int heap_qsort_compare_subblock(const void* a1, const void* a2) { HeapBlockHeader* header1 = *(HeapBlockHeader**)a1; HeapBlockHeader* header2 = *(HeapBlockHeader**)a2; return header1->size - header2->size; } // NOTE: Inlined. // // 0x453F4C static bool heap_build_fake_move_list(size_t count) { if (count > heap_fake_move_list_size) { int* indexes = (int*)mem_realloc(heap_fake_move_list, sizeof(*heap_fake_move_list) * count); if (indexes == NULL) { return false; } heap_fake_move_list_size = count; heap_fake_move_list = indexes; } return true; } ================================================ FILE: src/game/heap.h ================================================ #ifndef FALLOUT_GAME_HEAP_H_ #define FALLOUT_GAME_HEAP_H_ #include typedef struct HeapHandle { unsigned int state; unsigned char* data; } HeapHandle; typedef struct Heap { int size; int freeBlocks; int moveableBlocks; int lockedBlocks; int systemBlocks; int handlesLength; int freeSize; int moveableSize; int lockedSize; int systemSize; HeapHandle* handles; unsigned char* data; } Heap; bool heap_init(Heap* heap, int a2); bool heap_exit(Heap* heap); bool heap_allocate(Heap* heap, int* handleIndexPtr, int size, int a3); bool heap_deallocate(Heap* heap, int* handleIndexPtr); bool heap_lock(Heap* heap, int handleIndex, unsigned char** bufferPtr); bool heap_unlock(Heap* heap, int handleIndex); bool heap_stats(Heap* heap, char* dest); bool heap_validate(Heap* heap); #endif /* FALLOUT_GAME_HEAP_H_ */ ================================================ FILE: src/game/intface.c ================================================ #include "game/intface.h" #include #include #include "game/art.h" #include "game/anim.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/config.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/endgame.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/item.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/proto.h" #include "game/protinst.h" #include "game/proto_types.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #define INDICATOR_BAR_X 0 #define INDICATOR_BAR_Y 358 // The width of connectors in the indicator box. // // There are male connectors on the left, and female connectors on the right. // When displaying series of boxes they appear to be plugged into a chain. #define INDICATOR_BOX_CONNECTOR_WIDTH 3 // Minimum radiation amount to display RADIATED indicator. #define RADATION_INDICATOR_THRESHOLD 65 // Minimum poison amount to display POISONED indicator. #define POISON_INDICATOR_THRESHOLD 0 // The values of it's members are offsets to beginning of numbers in // numbers.frm. typedef enum InterfaceNumbersColor { INTERFACE_NUMBERS_COLOR_WHITE = 0, INTERFACE_NUMBERS_COLOR_YELLOW = 120, INTERFACE_NUMBERS_COLOR_RED = 240, } InterfaceNumbersColor; #define INDICATOR_BOX_WIDTH 130 #define INDICATOR_BOX_HEIGHT 21 // The maximum number of indicator boxes the indicator bar can display. // // For unknown reason this number is 6, even though there are only 5 different // indicator types. In addition to that, default screen width 640px cannot hold // 6 boxes 130px each. #define INDICATOR_SLOTS_COUNT (6) // Available indicators. // // Indicator boxes in the bar are displayed according to the order of this enum. typedef enum Indicator { INDICATOR_ADDICT, INDICATOR_SNEAK, INDICATOR_LEVEL, INDICATOR_POISONED, INDICATOR_RADIATED, INDICATOR_COUNT, } Indicator; // Provides metadata about indicator boxes. typedef struct IndicatorDescription { // An identifier of title in `intrface.msg`. int title; // A flag denoting this box represents something harmful to the player. It // affects color of the title. bool isBad; // Prerendered indicator data. // // This value is provided at runtime during indicator box initialization. // It includes indicator box background with it's title positioned in the // center and is green colored if indicator is good, or red otherwise, as // denoted by [isBad] property. unsigned char* data; } IndicatorDescription; typedef struct InterfaceItemState { Object* item; unsigned char isDisabled; unsigned char isWeapon; int primaryHitMode; int secondaryHitMode; int action; int itemFid; } InterfaceItemState; static int intface_init_items(); static int intface_redraw_items(); static int intface_redraw_items_callback(Object* a1, Object* a2); static int intface_change_fid_callback(Object* a1, Object* a2); static void intface_change_fid_animate(int previousWeaponAnimationCode, int weaponAnimationCode); static int intface_create_end_turn_button(); static int intface_destroy_end_turn_button(); static int intface_create_end_combat_button(); static int intface_destroy_end_combat_button(); static void intface_draw_ammo_lights(int x, int ratio); static int intface_item_reload(); static void intface_rotate_numbers(int x, int y, int previousValue, int value, int offset, int delay); static int intface_fatal_error(int rc); static int construct_box_bar_win(); static void deconstruct_box_bar_win(); static void reset_box_bar_win(); static int bbox_comp(const void* a, const void* b); static void draw_bboxes(int count); static bool add_bar_box(int indicator); // 0x518F08 static bool insideInit = false; // 0x518F0C static bool intface_fid_is_changing = false; // 0x518F10 static bool intfaceEnabled = false; // 0x518F14 static bool intfaceHidden = false; // 0x518F18 static int inventoryButton = -1; // 0x518F1C static CacheEntry* inventoryButtonUpKey = NULL; // 0x518F20 static CacheEntry* inventoryButtonDownKey = NULL; // 0x518F24 static int optionsButton = -1; // 0x518F28 static CacheEntry* optionsButtonUpKey = NULL; // 0x518F2C static CacheEntry* optionsButtonDownKey = NULL; // 0x518F30 static int skilldexButton = -1; // 0x518F34 static CacheEntry* skilldexButtonUpKey = NULL; // 0x518F38 static CacheEntry* skilldexButtonDownKey = NULL; // 0x518F3C static CacheEntry* skilldexButtonMaskKey = NULL; // 0x518F40 static int automapButton = -1; // 0x518F44 static CacheEntry* automapButtonUpKey = NULL; // 0x518F48 static CacheEntry* automapButtonDownKey = NULL; // 0x518F4C static CacheEntry* automapButtonMaskKey = NULL; // 0x518F50 static int pipboyButton = -1; // 0x518F54 static CacheEntry* pipboyButtonUpKey = NULL; // 0x518F58 static CacheEntry* pipboyButtonDownKey = NULL; // 0x518F5C static int characterButton = -1; // 0x518F60 static CacheEntry* characterButtonUpKey = NULL; // 0x518F64 static CacheEntry* characterButtonDownKey = NULL; // 0x518F68 static int itemButton = -1; // 0x518F6C static CacheEntry* itemButtonUpKey = NULL; // 0x518F70 static CacheEntry* itemButtonDownKey = NULL; // 0x518F74 static CacheEntry* itemButtonDisabledKey = NULL; // 0x518F78 static int itemCurrentItem = HAND_LEFT; // 0x518F7C static Rect itemButtonRect = { 267, 26, 455, 93 }; // 0x518F8C static int toggleButton = -1; // 0x518F90 static CacheEntry* toggleButtonUpKey = NULL; // 0x518F94 static CacheEntry* toggleButtonDownKey = NULL; // 0x518F98 static CacheEntry* toggleButtonMaskKey = NULL; // 0x518F9C static bool endWindowOpen = false; // Combat mode curtains rect. // // 0x518FA0 static Rect endWindowRect = { 580, 38, 637, 96 }; // 0x518FB0 static int endTurnButton = -1; // 0x518FB4 static CacheEntry* endTurnButtonUpKey = NULL; // 0x518FB8 static CacheEntry* endTurnButtonDownKey = NULL; // 0x518FBC static int endCombatButton = -1; // 0x518FC0 static CacheEntry* endCombatButtonUpKey = NULL; // 0x518FC4 static CacheEntry* endCombatButtonDownKey = NULL; // 0x518FC8 static unsigned char* moveLightGreen = NULL; // 0x518FCC static unsigned char* moveLightYellow = NULL; // 0x518FD0 static unsigned char* moveLightRed = NULL; // 0x518FD4 static Rect movePointRect = { 316, 14, 406, 19 }; // 0x518FE4 static unsigned char* numbersBuffer = NULL; // 0x518FE8 static IndicatorDescription bbox[INDICATOR_COUNT] = { { 102, true, NULL }, // ADDICT { 100, false, NULL }, // SNEAK { 101, false, NULL }, // LEVEL { 103, true, NULL }, // POISONED { 104, true, NULL }, // RADIATED }; // 0x519024 int interfaceWindow = -1; // 0x519028 int bar_window = -1; // Each slot contains one of indicators or -1 if slot is empty. // // 0x5970E0 static int bboxslot[INDICATOR_SLOTS_COUNT]; // 0x5970F8 static InterfaceItemState itemButtonItems[HAND_COUNT]; // 0x597128 static CacheEntry* moveLightYellowKey; // 0x59712C static CacheEntry* moveLightRedKey; // 0x597130 static CacheEntry* numbersKey; // 0x597138 static bool box_status_flag; // 0x59713C static unsigned char* toggleButtonUp; // 0x597140 static CacheEntry* moveLightGreenKey; // 0x597144 static unsigned char* endCombatButtonUp; // 0x597148 static unsigned char* endCombatButtonDown; // 0x59714C static unsigned char* toggleButtonDown; // 0x597150 static unsigned char* endTurnButtonDown; // 0x597154 static unsigned char itemButtonDown[188 * 67]; // 0x59A288 static unsigned char* endTurnButtonUp; // 0x59A28C static unsigned char* toggleButtonMask; // 0x59A290 static unsigned char* characterButtonUp; // 0x59A294 static unsigned char* itemButtonUpBlank; // 0x59A298 static unsigned char* itemButtonDisabled; // 0x59A29C static unsigned char* automapButtonDown; // 0x59A2A0 static unsigned char* pipboyButtonUp; // 0x59A2A4 static unsigned char* characterButtonDown; // 0x59A2A8 static unsigned char* itemButtonDownBlank; // 0x59A2AC static unsigned char* pipboyButtonDown; // 0x59A2B0 static unsigned char* automapButtonMask; // 0x59A2B4 static unsigned char itemButtonUp[188 * 67]; // 0x59D3E8 static unsigned char* automapButtonUp; // 0x59D3EC static unsigned char* skilldexButtonMask; // 0x59D3F0 static unsigned char* skilldexButtonDown; // 0x59D3F4 static unsigned char* interfaceBuffer; // 0x59D3F8 static unsigned char* inventoryButtonUp; // 0x59D3FC static unsigned char* optionsButtonUp; // 0x59D400 static unsigned char* optionsButtonDown; // 0x59D404 static unsigned char* skilldexButtonUp; // 0x59D408 static unsigned char* inventoryButtonDown; // A slice of main interface background containing 10 shadowed action point // dots. In combat mode individual colored dots are rendered on top of this // background. // // This buffer is initialized once and does not change throughout the game. // // 0x59D40C static unsigned char movePointBackground[90 * 5]; // 0x45D880 int intface_init() { int fid; CacheEntry* backgroundFrmHandle; unsigned char* backgroundFrmData; if (interfaceWindow != -1) { return -1; } insideInit = 1; int interfaceBarWindowX = 0; int interfaceBarWindowY = 480 - INTERFACE_BAR_HEIGHT - 1; interfaceWindow = win_add(interfaceBarWindowX, interfaceBarWindowY, INTERFACE_BAR_WIDTH, INTERFACE_BAR_HEIGHT, colorTable[0], WINDOW_HIDDEN); if (interfaceWindow == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } interfaceBuffer = win_get_buf(interfaceWindow); if (interfaceBuffer == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 16, 0, 0, 0); backgroundFrmData = art_ptr_lock_data(fid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } buf_to_buf(backgroundFrmData, INTERFACE_BAR_WIDTH, INTERFACE_BAR_HEIGHT - 1, INTERFACE_BAR_WIDTH, interfaceBuffer, 640); art_ptr_unlock(backgroundFrmHandle); fid = art_id(OBJ_TYPE_INTERFACE, 47, 0, 0, 0); inventoryButtonUp = art_ptr_lock_data(fid, 0, 0, &inventoryButtonUpKey); if (inventoryButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 46, 0, 0, 0); inventoryButtonDown = art_ptr_lock_data(fid, 0, 0, &inventoryButtonDownKey); if (inventoryButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } inventoryButton = win_register_button(interfaceWindow, 211, 41, 32, 21, -1, -1, -1, KEY_LOWERCASE_I, inventoryButtonUp, inventoryButtonDown, NULL, 0); if (inventoryButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_sound_func(inventoryButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 18, 0, 0, 0); optionsButtonUp = art_ptr_lock_data(fid, 0, 0, &optionsButtonUpKey); if (optionsButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 17, 0, 0, 0); optionsButtonDown = art_ptr_lock_data(fid, 0, 0, &optionsButtonDownKey); if (optionsButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } optionsButton = win_register_button(interfaceWindow, 210, 62, 34, 34, -1, -1, -1, KEY_LOWERCASE_O, optionsButtonUp, optionsButtonDown, NULL, 0); if (optionsButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_sound_func(optionsButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0); skilldexButtonUp = art_ptr_lock_data(fid, 0, 0, &skilldexButtonUpKey); if (skilldexButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 7, 0, 0, 0); skilldexButtonDown = art_ptr_lock_data(fid, 0, 0, &skilldexButtonDownKey); if (skilldexButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0); skilldexButtonMask = art_ptr_lock_data(fid, 0, 0, &skilldexButtonMaskKey); if (skilldexButtonMask == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } skilldexButton = win_register_button(interfaceWindow, 523, 6, 22, 21, -1, -1, -1, KEY_LOWERCASE_S, skilldexButtonUp, skilldexButtonDown, NULL, BUTTON_FLAG_TRANSPARENT); if (skilldexButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_mask(skilldexButton, skilldexButtonMask); win_register_button_sound_func(skilldexButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 13, 0, 0, 0); automapButtonUp = art_ptr_lock_data(fid, 0, 0, &automapButtonUpKey); if (automapButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 10, 0, 0, 0); automapButtonDown = art_ptr_lock_data(fid, 0, 0, &automapButtonDownKey); if (automapButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 13, 0, 0, 0); automapButtonMask = art_ptr_lock_data(fid, 0, 0, &automapButtonMaskKey); if (automapButtonMask == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } automapButton = win_register_button(interfaceWindow, 526, 40, 41, 19, -1, -1, -1, KEY_TAB, automapButtonUp, automapButtonDown, NULL, BUTTON_FLAG_TRANSPARENT); if (automapButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_mask(automapButton, automapButtonMask); win_register_button_sound_func(automapButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 59, 0, 0, 0); pipboyButtonUp = art_ptr_lock_data(fid, 0, 0, &pipboyButtonUpKey); if (pipboyButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 58, 0, 0, 0); pipboyButtonDown = art_ptr_lock_data(fid, 0, 0, &pipboyButtonDownKey); if (pipboyButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } pipboyButton = win_register_button(interfaceWindow, 526, 78, 41, 19, -1, -1, -1, KEY_LOWERCASE_P, pipboyButtonUp, pipboyButtonDown, NULL, 0); if (pipboyButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_mask(pipboyButton, automapButtonMask); win_register_button_sound_func(pipboyButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 57, 0, 0, 0); characterButtonUp = art_ptr_lock_data(fid, 0, 0, &characterButtonUpKey); if (characterButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 56, 0, 0, 0); characterButtonDown = art_ptr_lock_data(fid, 0, 0, &characterButtonDownKey); if (characterButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } characterButton = win_register_button(interfaceWindow, 526, 59, 41, 19, -1, -1, -1, KEY_LOWERCASE_C, characterButtonUp, characterButtonDown, NULL, 0); if (characterButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_mask(characterButton, automapButtonMask); win_register_button_sound_func(characterButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 32, 0, 0, 0); itemButtonUpBlank = art_ptr_lock_data(fid, 0, 0, &itemButtonUpKey); if (itemButtonUpBlank == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 31, 0, 0, 0); itemButtonDownBlank = art_ptr_lock_data(fid, 0, 0, &itemButtonDownKey); if (itemButtonDownBlank == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 73, 0, 0, 0); itemButtonDisabled = art_ptr_lock_data(fid, 0, 0, &itemButtonDisabledKey); if (itemButtonDisabled == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } memcpy(itemButtonUp, itemButtonUpBlank, sizeof(itemButtonUp)); memcpy(itemButtonDown, itemButtonDownBlank, sizeof(itemButtonDown)); itemButton = win_register_button(interfaceWindow, 267, 26, 188, 67, -1, -1, -1, -20, itemButtonUp, itemButtonDown, NULL, BUTTON_FLAG_TRANSPARENT); if (itemButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_right_button(itemButton, -1, KEY_LOWERCASE_N, NULL, NULL); win_register_button_sound_func(itemButton, gsound_lrg_butt_press, gsound_lrg_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0); toggleButtonUp = art_ptr_lock_data(fid, 0, 0, &toggleButtonUpKey); if (toggleButtonUp == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 7, 0, 0, 0); toggleButtonDown = art_ptr_lock_data(fid, 0, 0, &toggleButtonDownKey); if (toggleButtonDown == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 6, 0, 0, 0); toggleButtonMask = art_ptr_lock_data(fid, 0, 0, &toggleButtonMaskKey); if (toggleButtonMask == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } // Swap hands button toggleButton = win_register_button(interfaceWindow, 218, 6, 22, 21, -1, -1, -1, KEY_LOWERCASE_B, toggleButtonUp, toggleButtonDown, NULL, BUTTON_FLAG_TRANSPARENT); if (toggleButton == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } win_register_button_mask(toggleButton, toggleButtonMask); win_register_button_sound_func(toggleButton, gsound_med_butt_press, gsound_med_butt_release); fid = art_id(OBJ_TYPE_INTERFACE, 82, 0, 0, 0); numbersBuffer = art_ptr_lock_data(fid, 0, 0, &numbersKey); if (numbersBuffer == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 83, 0, 0, 0); moveLightGreen = art_ptr_lock_data(fid, 0, 0, &moveLightGreenKey); if (moveLightGreen == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 84, 0, 0, 0); moveLightYellow = art_ptr_lock_data(fid, 0, 0, &moveLightYellowKey); if (moveLightYellow == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } fid = art_id(OBJ_TYPE_INTERFACE, 85, 0, 0, 0); moveLightRed = art_ptr_lock_data(fid, 0, 0, &moveLightRedKey); if (moveLightRed == NULL) { // NOTE: Uninline. return intface_fatal_error(-1); } buf_to_buf(interfaceBuffer + 640 * 14 + 316, 90, 5, 640, movePointBackground, 90); if (construct_box_bar_win() == -1) { // NOTE: Uninline. return intface_fatal_error(-1); } itemCurrentItem = HAND_LEFT; // NOTE: Uninline. intface_init_items(); display_init(); intfaceEnabled = true; insideInit = false; intfaceHidden = 1; return 0; } // 0x45E3D0 void intface_reset() { intface_enable(); // NOTE: Uninline. intface_hide(); display_reset(); // NOTE: Uninline. reset_box_bar_win(); itemCurrentItem = 0; } // 0x45E440 void intface_exit() { if (interfaceWindow != -1) { display_exit(); if (moveLightRed != NULL) { art_ptr_unlock(moveLightRedKey); moveLightRed = NULL; } if (moveLightYellow != NULL) { art_ptr_unlock(moveLightYellowKey); moveLightYellow = NULL; } if (moveLightGreen != NULL) { art_ptr_unlock(moveLightGreenKey); moveLightGreen = NULL; } if (numbersBuffer != NULL) { art_ptr_unlock(numbersKey); numbersBuffer = NULL; } if (toggleButton != -1) { win_delete_button(toggleButton); toggleButton = -1; } if (toggleButtonMask != NULL) { art_ptr_unlock(toggleButtonMaskKey); toggleButtonMaskKey = NULL; toggleButtonMask = NULL; } if (toggleButtonDown != NULL) { art_ptr_unlock(toggleButtonDownKey); toggleButtonDownKey = NULL; toggleButtonDown = NULL; } if (toggleButtonUp != NULL) { art_ptr_unlock(toggleButtonUpKey); toggleButtonUpKey = NULL; toggleButtonUp = NULL; } if (itemButton != -1) { win_delete_button(itemButton); itemButton = -1; } if (itemButtonDisabled != NULL) { art_ptr_unlock(itemButtonDisabledKey); itemButtonDisabledKey = NULL; itemButtonDisabled = NULL; } if (itemButtonDownBlank != NULL) { art_ptr_unlock(itemButtonDownKey); itemButtonDownKey = NULL; itemButtonDownBlank = NULL; } if (itemButtonUpBlank != NULL) { art_ptr_unlock(itemButtonUpKey); itemButtonUpKey = NULL; itemButtonUpBlank = NULL; } if (characterButton != -1) { win_delete_button(characterButton); characterButton = -1; } if (characterButtonDown != NULL) { art_ptr_unlock(characterButtonDownKey); characterButtonDownKey = NULL; characterButtonDown = NULL; } if (characterButtonUp != NULL) { art_ptr_unlock(characterButtonUpKey); characterButtonUpKey = NULL; characterButtonUp = NULL; } if (pipboyButton != -1) { win_delete_button(pipboyButton); pipboyButton = -1; } if (pipboyButtonDown != NULL) { art_ptr_unlock(pipboyButtonDownKey); pipboyButtonDownKey = NULL; pipboyButtonDown = NULL; } if (pipboyButtonUp != NULL) { art_ptr_unlock(pipboyButtonUpKey); pipboyButtonUpKey = NULL; pipboyButtonUp = NULL; } if (automapButton != -1) { win_delete_button(automapButton); automapButton = -1; } if (automapButtonMask != NULL) { art_ptr_unlock(automapButtonMaskKey); automapButtonMaskKey = NULL; automapButtonMask = NULL; } if (automapButtonDown != NULL) { art_ptr_unlock(automapButtonDownKey); automapButtonDownKey = NULL; automapButtonDown = NULL; } if (automapButtonUp != NULL) { art_ptr_unlock(automapButtonUpKey); automapButtonUpKey = NULL; automapButtonUp = NULL; } if (skilldexButton != -1) { win_delete_button(skilldexButton); skilldexButton = -1; } if (skilldexButtonMask != NULL) { art_ptr_unlock(skilldexButtonMaskKey); skilldexButtonMaskKey = NULL; skilldexButtonMask = NULL; } if (skilldexButtonDown != NULL) { art_ptr_unlock(skilldexButtonDownKey); skilldexButtonDownKey = NULL; skilldexButtonDown = NULL; } if (skilldexButtonUp != NULL) { art_ptr_unlock(skilldexButtonUpKey); skilldexButtonUpKey = NULL; skilldexButtonUp = NULL; } if (optionsButton != -1) { win_delete_button(optionsButton); optionsButton = -1; } if (optionsButtonDown != NULL) { art_ptr_unlock(optionsButtonDownKey); optionsButtonDownKey = NULL; optionsButtonDown = NULL; } if (optionsButtonUp != NULL) { art_ptr_unlock(optionsButtonUpKey); optionsButtonUpKey = NULL; optionsButtonUp = NULL; } if (inventoryButton != -1) { win_delete_button(inventoryButton); inventoryButton = -1; } if (inventoryButtonDown != NULL) { art_ptr_unlock(inventoryButtonDownKey); inventoryButtonDownKey = NULL; inventoryButtonDown = NULL; } if (inventoryButtonUp != NULL) { art_ptr_unlock(inventoryButtonUpKey); inventoryButtonUpKey = NULL; inventoryButtonUp = NULL; } if (interfaceWindow != -1) { win_delete(interfaceWindow); interfaceWindow = -1; } } deconstruct_box_bar_win(); } // 0x45E860 int intface_load(File* stream) { if (interfaceWindow == -1) { if (intface_init() == -1) { return -1; } } int interfaceBarEnabled; if (db_freadInt(stream, &interfaceBarEnabled) == -1) return -1; int v2; if (db_freadInt(stream, &v2) == -1) return -1; int interfaceCurrentHand; if (db_freadInt(stream, &interfaceCurrentHand) == -1) return -1; bool interfaceBarEndButtonsIsVisible; if (fileReadBool(stream, &interfaceBarEndButtonsIsVisible) == -1) return -1; if (!intfaceEnabled) { intface_enable(); } if (v2) { // NOTE: Uninline. intface_hide(); } else { intface_show(); } intface_update_hit_points(false); intface_update_ac(false); itemCurrentItem = interfaceCurrentHand; intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); if (interfaceBarEndButtonsIsVisible != endWindowOpen) { if (interfaceBarEndButtonsIsVisible) { intface_end_window_open(false); } else { intface_end_window_close(false); } } if (!interfaceBarEnabled) { intface_disable(); } refresh_box_bar_win(); win_draw(interfaceWindow); return 0; } // 0x45E988 int intface_save(File* stream) { if (interfaceWindow == -1) { return -1; } if (db_fwriteInt(stream, intfaceEnabled) == -1) return -1; if (db_fwriteInt(stream, intfaceHidden) == -1) return -1; if (db_fwriteInt(stream, itemCurrentItem) == -1) return -1; if (db_fwriteInt(stream, endWindowOpen) == -1) return -1; return 0; } // NOTE: Inlined. // // 0x45E9E0 void intface_hide() { if (interfaceWindow != -1) { if (!intfaceHidden) { win_hide(interfaceWindow); intfaceHidden = 1; } } refresh_box_bar_win(); } // 0x45EA10 void intface_show() { if (interfaceWindow != -1) { if (intfaceHidden) { intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); intface_update_hit_points(false); intface_update_ac(false); win_show(interfaceWindow); intfaceHidden = false; } } refresh_box_bar_win(); } // NOTE: Unused. // // 0x45EA5C int intface_is_hidden() { return intfaceHidden; } // 0x45EA64 void intface_enable() { if (!intfaceEnabled) { win_enable_button(inventoryButton); win_enable_button(optionsButton); win_enable_button(skilldexButton); win_enable_button(automapButton); win_enable_button(pipboyButton); win_enable_button(characterButton); if (itemButtonItems[itemCurrentItem].isDisabled == 0) { win_enable_button(itemButton); } win_enable_button(endTurnButton); win_enable_button(endCombatButton); display_enable(); intfaceEnabled = true; } } // 0x45EAFC void intface_disable() { if (intfaceEnabled) { display_disable(); win_disable_button(inventoryButton); win_disable_button(optionsButton); win_disable_button(skilldexButton); win_disable_button(automapButton); win_disable_button(pipboyButton); win_disable_button(characterButton); if (itemButtonItems[itemCurrentItem].isDisabled == 0) { win_disable_button(itemButton); } win_disable_button(endTurnButton); win_disable_button(endCombatButton); intfaceEnabled = false; } } // 0x45EB90 bool intface_is_enabled() { return intfaceEnabled; } // 0x45EB98 void intface_redraw() { if (interfaceWindow != -1) { intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); intface_update_hit_points(false); intface_update_ac(false); refresh_box_bar_win(); win_draw(interfaceWindow); } refresh_box_bar_win(); } // Render hit points. // // 0x45EBD8 void intface_update_hit_points(bool animate) { // Last hit points rendered in interface. // // Used to animate changes. // // 0x51902C static int last_points = 0; // Last color used to render hit points in interface. // // Used to animate changes. // // 0x519030 static int last_points_color = INTERFACE_NUMBERS_COLOR_RED; if (interfaceWindow == -1) { return; } int hp = critter_get_hits(obj_dude); int maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); int red = (int)((double)maxHp * 0.25); int yellow = (int)((double)maxHp * 0.5); int color; if (hp < red) { color = INTERFACE_NUMBERS_COLOR_RED; } else if (hp < yellow) { color = INTERFACE_NUMBERS_COLOR_YELLOW; } else { color = INTERFACE_NUMBERS_COLOR_WHITE; } int v1[4]; int v2[3]; int count = 1; v1[0] = last_points; v2[0] = last_points_color; if (last_points_color != color) { if (hp >= last_points) { if (last_points < red && hp >= red) { v1[count] = red; v2[count] = INTERFACE_NUMBERS_COLOR_YELLOW; count += 1; } if (last_points < yellow && hp >= yellow) { v1[count] = yellow; v2[count] = INTERFACE_NUMBERS_COLOR_WHITE; count += 1; } } else { if (last_points >= yellow && hp < yellow) { v1[count] = yellow; v2[count] = INTERFACE_NUMBERS_COLOR_YELLOW; count += 1; } if (last_points >= red && hp < red) { v1[count] = red; v2[count] = INTERFACE_NUMBERS_COLOR_RED; count += 1; } } } v1[count] = hp; if (animate) { int delay = 250 / (abs(last_points - hp) + 1); for (int index = 0; index < count; index++) { intface_rotate_numbers(473, 40, v1[index], v1[index + 1], v2[index], delay); } } else { intface_rotate_numbers(473, 40, last_points, hp, color, 0); } last_points = hp; last_points_color = color; } // Render armor class. // // 0x45EDA8 void intface_update_ac(bool animate) { // Last armor class rendered in interface. // // Used to animate changes. // // 0x519034 static int last_ac = 0; int armorClass = critterGetStat(obj_dude, STAT_ARMOR_CLASS); int delay = 0; if (animate) { delay = 250 / (abs(last_ac - armorClass) + 1); } intface_rotate_numbers(473, 75, last_ac, armorClass, 0, delay); last_ac = armorClass; } // 0x45EE0C void intface_update_move_points(int actionPointsLeft, int bonusActionPoints) { unsigned char* frmData; if (interfaceWindow == -1) { return; } buf_to_buf(movePointBackground, 90, 5, 90, interfaceBuffer + 14 * 640 + 316, 640); if (actionPointsLeft == -1) { frmData = moveLightRed; actionPointsLeft = 10; bonusActionPoints = 0; } else { frmData = moveLightGreen; if (actionPointsLeft < 0) { actionPointsLeft = 0; } if (actionPointsLeft > 10) { actionPointsLeft = 10; } if (bonusActionPoints >= 0) { if (actionPointsLeft + bonusActionPoints > 10) { bonusActionPoints = 10 - actionPointsLeft; } } else { bonusActionPoints = 0; } } int index; for (index = 0; index < actionPointsLeft; index++) { buf_to_buf(frmData, 5, 5, 5, interfaceBuffer + 14 * 640 + 316 + index * 9, 640); } for (; index < (actionPointsLeft + bonusActionPoints); index++) { buf_to_buf(moveLightYellow, 5, 5, 5, interfaceBuffer + 14 * 640 + 316 + index * 9, 640); } if (!insideInit) { win_draw_rect(interfaceWindow, &movePointRect); } } // 0x45EF6C int intface_get_attack(int* hitMode, bool* aiming) { if (interfaceWindow == -1) { return -1; } *aiming = false; switch (itemButtonItems[itemCurrentItem].action) { case INTERFACE_ITEM_ACTION_PRIMARY_AIMING: *aiming = true; // FALLTHROUGH case INTERFACE_ITEM_ACTION_PRIMARY: *hitMode = itemButtonItems[itemCurrentItem].primaryHitMode; return 0; case INTERFACE_ITEM_ACTION_SECONDARY_AIMING: *aiming = true; // FALLTHROUGH case INTERFACE_ITEM_ACTION_SECONDARY: *hitMode = itemButtonItems[itemCurrentItem].secondaryHitMode; return 0; } return -1; } // 0x45EFEC int intface_update_items(bool animated, int leftItemAction, int rightItemAction) { if (map_bk_processes_are_disabled()) { animated = false; } if (interfaceWindow == -1) { return -1; } Object* oldCurrentItem = itemButtonItems[itemCurrentItem].item; InterfaceItemState* leftItemState = &(itemButtonItems[HAND_LEFT]); Object* item1 = inven_left_hand(obj_dude); if (item1 == leftItemState->item && leftItemState->item != NULL) { if (leftItemState->item != NULL) { leftItemState->isDisabled = item_grey(item1); leftItemState->itemFid = item_inv_fid(item1); } } else { leftItemState->item = item1; if (item1 != NULL) { leftItemState->isDisabled = item_grey(item1); leftItemState->primaryHitMode = HIT_MODE_LEFT_WEAPON_PRIMARY; leftItemState->secondaryHitMode = HIT_MODE_LEFT_WEAPON_SECONDARY; leftItemState->isWeapon = item_get_type(item1) == ITEM_TYPE_WEAPON; if (leftItemAction == INTERFACE_ITEM_ACTION_DEFAULT) { if (leftItemState->isWeapon != 0) { leftItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; } else { leftItemState->action = INTERFACE_ITEM_ACTION_USE; } } else { leftItemState->action = leftItemAction; } leftItemState->itemFid = item_inv_fid(item1); } else { leftItemState->isDisabled = 0; leftItemState->isWeapon = 1; leftItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; leftItemState->itemFid = -1; int unarmed = skill_level(obj_dude, SKILL_UNARMED); int agility = critterGetStat(obj_dude, STAT_AGILITY); int strength = critterGetStat(obj_dude, STAT_STRENGTH); int level = stat_pc_get(PC_STAT_LEVEL); if (unarmed > 99 && agility > 6 && strength > 4 && level > 8) { leftItemState->primaryHitMode = HIT_MODE_HAYMAKER; } else if (unarmed > 74 && agility > 5 && strength > 4 && level > 5) { leftItemState->primaryHitMode = HIT_MODE_HAMMER_PUNCH; } else if (unarmed > 54 && agility > 5) { leftItemState->primaryHitMode = HIT_MODE_STRONG_PUNCH; } else { leftItemState->primaryHitMode = HIT_MODE_PUNCH; } if (unarmed > 129 && agility > 6 && strength > 4 && level > 15) { leftItemState->secondaryHitMode = HIT_MODE_PIERCING_STRIKE; } else if (unarmed > 114 && agility > 6 && strength > 4 && level > 11) { leftItemState->secondaryHitMode = HIT_MODE_PALM_STRIKE; } else if (unarmed > 74 && agility > 6 && strength > 4 && level > 4) { leftItemState->secondaryHitMode = HIT_MODE_JAB; } else { leftItemState->secondaryHitMode = HIT_MODE_PUNCH; } } } InterfaceItemState* rightItemState = &(itemButtonItems[HAND_RIGHT]); Object* item2 = inven_right_hand(obj_dude); if (item2 == rightItemState->item && rightItemState->item != NULL) { if (rightItemState->item != NULL) { rightItemState->isDisabled = item_grey(rightItemState->item); rightItemState->itemFid = item_inv_fid(rightItemState->item); } } else { rightItemState->item = item2; if (item2 != NULL) { rightItemState->isDisabled = item_grey(item2); rightItemState->primaryHitMode = HIT_MODE_RIGHT_WEAPON_PRIMARY; rightItemState->secondaryHitMode = HIT_MODE_RIGHT_WEAPON_SECONDARY; rightItemState->isWeapon = item_get_type(item2) == ITEM_TYPE_WEAPON; if (rightItemAction == INTERFACE_ITEM_ACTION_DEFAULT) { if (rightItemState->isWeapon != 0) { rightItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; } else { rightItemState->action = INTERFACE_ITEM_ACTION_USE; } } else { rightItemState->action = rightItemAction; } rightItemState->itemFid = item_inv_fid(item2); } else { rightItemState->isDisabled = 0; rightItemState->isWeapon = 1; rightItemState->action = INTERFACE_ITEM_ACTION_PRIMARY; rightItemState->itemFid = -1; int unarmed = skill_level(obj_dude, SKILL_UNARMED); int agility = critterGetStat(obj_dude, STAT_AGILITY); int strength = critterGetStat(obj_dude, STAT_STRENGTH); int level = stat_pc_get(PC_STAT_LEVEL); if (unarmed > 79 && agility > 5 && strength > 5 && level > 8) { rightItemState->primaryHitMode = HIT_MODE_POWER_KICK; } else if (unarmed > 59 && agility > 5 && level > 5) { rightItemState->primaryHitMode = HIT_MODE_SNAP_KICK; } else if (unarmed > 39 && agility > 5) { rightItemState->primaryHitMode = HIT_MODE_STRONG_KICK; } else { rightItemState->primaryHitMode = HIT_MODE_KICK; } if (unarmed > 124 && agility > 7 && strength > 5 && level > 14) { rightItemState->secondaryHitMode = HIT_MODE_PIERCING_KICK; } else if (unarmed > 99 && agility > 6 && strength > 5 && level > 11) { rightItemState->secondaryHitMode = HIT_MODE_HOOK_KICK; } else if (unarmed > 59 && agility > 6 && strength > 5 && level > 5) { rightItemState->secondaryHitMode = HIT_MODE_HIP_KICK; } else { rightItemState->secondaryHitMode = HIT_MODE_KICK; } } } if (animated) { Object* newCurrentItem = itemButtonItems[itemCurrentItem].item; if (newCurrentItem != oldCurrentItem) { int animationCode = 0; if (newCurrentItem != NULL) { if (item_get_type(newCurrentItem) == ITEM_TYPE_WEAPON) { animationCode = item_w_anim_code(newCurrentItem); } } intface_change_fid_animate((obj_dude->fid & 0xF000) >> 12, animationCode); return 0; } } intface_redraw_items(); return 0; } // 0x45F404 int intface_toggle_items(bool animated) { if (interfaceWindow == -1) { return -1; } itemCurrentItem = 1 - itemCurrentItem; if (animated) { Object* item = itemButtonItems[itemCurrentItem].item; int animationCode = 0; if (item != NULL) { if (item_get_type(item) == ITEM_TYPE_WEAPON) { animationCode = item_w_anim_code(item); } } intface_change_fid_animate((obj_dude->fid & 0xF000) >> 12, animationCode); } else { intface_redraw_items(); } int mode = gmouse_3d_get_mode(); if (mode == GAME_MOUSE_MODE_CROSSHAIR || mode == GAME_MOUSE_MODE_USE_CROSSHAIR) { gmouse_3d_set_mode(GAME_MOUSE_MODE_MOVE); } return 0; } // 0x45F4B4 int intface_get_item_states(int* leftItemAction, int* rightItemAction) { *leftItemAction = itemButtonItems[HAND_LEFT].action; *rightItemAction = itemButtonItems[HAND_RIGHT].action; return 0; } // 0x45F4E0 int intface_toggle_item_state() { if (interfaceWindow == -1) { return -1; } InterfaceItemState* itemState = &(itemButtonItems[itemCurrentItem]); int oldAction = itemState->action; if (itemState->isWeapon != 0) { bool done = false; while (!done) { itemState->action++; switch (itemState->action) { case INTERFACE_ITEM_ACTION_PRIMARY: done = true; break; case INTERFACE_ITEM_ACTION_PRIMARY_AIMING: if (item_w_called_shot(obj_dude, itemState->primaryHitMode)) { done = true; } break; case INTERFACE_ITEM_ACTION_SECONDARY: if (itemState->secondaryHitMode != HIT_MODE_PUNCH && itemState->secondaryHitMode != HIT_MODE_KICK && item_w_subtype(itemState->item, itemState->secondaryHitMode) != ATTACK_TYPE_NONE) { done = true; } break; case INTERFACE_ITEM_ACTION_SECONDARY_AIMING: if (itemState->secondaryHitMode != HIT_MODE_PUNCH && itemState->secondaryHitMode != HIT_MODE_KICK && item_w_subtype(itemState->item, itemState->secondaryHitMode) != ATTACK_TYPE_NONE && item_w_called_shot(obj_dude, itemState->secondaryHitMode)) { done = true; } break; case INTERFACE_ITEM_ACTION_RELOAD: if (item_w_max_ammo(itemState->item) != item_w_curr_ammo(itemState->item)) { done = true; } break; case INTERFACE_ITEM_ACTION_COUNT: itemState->action = INTERFACE_ITEM_ACTION_USE; break; } } } if (oldAction != itemState->action) { intface_redraw_items(); } return 0; } // 0x45F5EC void intface_use_item() { if (interfaceWindow == -1) { return; } InterfaceItemState* ptr = &(itemButtonItems[itemCurrentItem]); if (ptr->isWeapon != 0) { if (ptr->action == INTERFACE_ITEM_ACTION_RELOAD) { if (isInCombat()) { int hitMode = itemCurrentItem == HAND_LEFT ? HIT_MODE_LEFT_WEAPON_RELOAD : HIT_MODE_RIGHT_WEAPON_RELOAD; int actionPointsRequired = item_mp_cost(obj_dude, hitMode, false); if (actionPointsRequired <= obj_dude->data.critter.combat.ap) { if (intface_item_reload() == 0) { if (actionPointsRequired > obj_dude->data.critter.combat.ap) { obj_dude->data.critter.combat.ap = 0; } else { obj_dude->data.critter.combat.ap -= actionPointsRequired; } intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } } } else { intface_item_reload(); } } else { gmouse_set_cursor(MOUSE_CURSOR_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_CROSSHAIR); if (!isInCombat()) { combat(NULL); } } } else if (proto_action_can_use_on(ptr->item->pid)) { gmouse_set_cursor(MOUSE_CURSOR_USE_CROSSHAIR); gmouse_3d_set_mode(GAME_MOUSE_MODE_USE_CROSSHAIR); } else if (obj_action_can_use(ptr->item)) { if (isInCombat()) { int actionPointsRequired = item_mp_cost(obj_dude, ptr->secondaryHitMode, false); if (actionPointsRequired <= obj_dude->data.critter.combat.ap) { obj_use_item(obj_dude, ptr->item); intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); if (actionPointsRequired > obj_dude->data.critter.combat.ap) { obj_dude->data.critter.combat.ap = 0; } else { obj_dude->data.critter.combat.ap -= actionPointsRequired; } intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } } else { obj_use_item(obj_dude, ptr->item); intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } } // 0x45F7FC int intface_is_item_right_hand() { return itemCurrentItem; } // 0x45F804 int intface_get_current_item(Object** itemPtr) { if (interfaceWindow == -1) { return -1; } *itemPtr = itemButtonItems[itemCurrentItem].item; return 0; } // 0x45F838 int intface_update_ammo_lights() { if (interfaceWindow == -1) { return -1; } InterfaceItemState* p = &(itemButtonItems[itemCurrentItem]); int ratio = 0; if (p->isWeapon != 0) { // calls sub_478674 twice, probably because if min/max kind macro int maximum = item_w_max_ammo(p->item); if (maximum > 0) { int current = item_w_curr_ammo(p->item); ratio = (int)((double)current / (double)maximum * 70.0); } } else { if (item_get_type(p->item) == ITEM_TYPE_MISC) { // calls sub_4793D0 twice, probably because if min/max kind macro int maximum = item_m_max_charges(p->item); if (maximum > 0) { int current = item_m_curr_charges(p->item); ratio = (int)((double)current / (double)maximum * 70.0); } } } intface_draw_ammo_lights(463, ratio); return 0; } // 0x45F96C void intface_end_window_open(bool animated) { if (interfaceWindow == -1) { return; } if (endWindowOpen) { return; } int fid = art_id(OBJ_TYPE_INTERFACE, 104, 0, 0, 0); CacheEntry* handle; Art* art = art_ptr_lock(fid, &handle); if (art == NULL) { return; } int frameCount = art_frame_max_frame(art); gsound_play_sfx_file("iciboxx1"); if (animated) { unsigned int delay = 1000 / art_frame_fps(art); int time = 0; int frame = 0; while (frame < frameCount) { if (elapsed_time(time) >= delay) { unsigned char* src = art_frame_data(art, frame, 0); if (src != NULL) { buf_to_buf(src, 57, 58, 57, interfaceBuffer + 640 * 38 + 580, 640); win_draw_rect(interfaceWindow, &endWindowRect); } time = get_time(); frame++; } gmouse_bk_process(); } } else { unsigned char* src = art_frame_data(art, frameCount - 1, 0); buf_to_buf(src, 57, 58, 57, interfaceBuffer + 640 * 38 + 580, 640); win_draw_rect(interfaceWindow, &endWindowRect); } art_ptr_unlock(handle); endWindowOpen = true; intface_create_end_turn_button(); intface_create_end_combat_button(); intface_end_buttons_disable(); } // 0x45FAC0 void intface_end_window_close(bool animated) { if (interfaceWindow == -1) { return; } if (!endWindowOpen) { return; } int fid = art_id(OBJ_TYPE_INTERFACE, 104, 0, 0, 0); CacheEntry* handle; Art* art = art_ptr_lock(fid, &handle); if (art == NULL) { return; } intface_destroy_end_turn_button(); intface_destroy_end_combat_button(); gsound_play_sfx_file("icibcxx1"); if (animated) { unsigned int delay = 1000 / art_frame_fps(art); unsigned int time = 0; int frame = art_frame_max_frame(art); while (frame != 0) { if (elapsed_time(time) >= delay) { unsigned char* src = art_frame_data(art, frame - 1, 0); unsigned char* dest = interfaceBuffer + 640 * 38 + 580; if (src != NULL) { buf_to_buf(src, 57, 58, 57, dest, 640); win_draw_rect(interfaceWindow, &endWindowRect); } time = get_time(); frame--; } gmouse_bk_process(); } } else { unsigned char* dest = interfaceBuffer + 640 * 38 + 580; unsigned char* src = art_frame_data(art, 0, 0); buf_to_buf(src, 57, 58, 57, dest, 640); win_draw_rect(interfaceWindow, &endWindowRect); } art_ptr_unlock(handle); endWindowOpen = false; } // 0x45FC04 void intface_end_buttons_enable() { if (endWindowOpen) { win_enable_button(endTurnButton); win_enable_button(endCombatButton); // endltgrn.frm - green lights around end turn/combat window int lightsFid = art_id(OBJ_TYPE_INTERFACE, 109, 0, 0, 0); CacheEntry* lightsFrmHandle; unsigned char* lightsFrmData = art_ptr_lock_data(lightsFid, 0, 0, &lightsFrmHandle); if (lightsFrmData == NULL) { return; } gsound_play_sfx_file("icombat2"); trans_buf_to_buf(lightsFrmData, 57, 58, 57, interfaceBuffer + 38 * 640 + 580, 640); win_draw_rect(interfaceWindow, &endWindowRect); art_ptr_unlock(lightsFrmHandle); } } // 0x45FC98 void intface_end_buttons_disable() { if (endWindowOpen) { win_disable_button(endTurnButton); win_disable_button(endCombatButton); CacheEntry* lightsFrmHandle; // endltred.frm - red lights around end turn/combat window int lightsFid = art_id(OBJ_TYPE_INTERFACE, 110, 0, 0, 0); unsigned char* lightsFrmData = art_ptr_lock_data(lightsFid, 0, 0, &lightsFrmHandle); if (lightsFrmData == NULL) { return; } gsound_play_sfx_file("icombat1"); trans_buf_to_buf(lightsFrmData, 57, 58, 57, interfaceBuffer + 38 * 640 + 580, 640); win_draw_rect(interfaceWindow, &endWindowRect); art_ptr_unlock(lightsFrmHandle); } } // NOTE: Inlined. // // 0x45FD2C static int intface_init_items() { // FIXME: For unknown reason these values initialized with -1. It's never // checked for -1, so I have no explanation for this. itemButtonItems[HAND_LEFT].item = (Object*)-1; itemButtonItems[HAND_RIGHT].item = (Object*)-1; return 0; } // 0x45FD88 static int intface_redraw_items() { if (interfaceWindow == -1) { return -1; } win_enable_button(itemButton); InterfaceItemState* itemState = &(itemButtonItems[itemCurrentItem]); int actionPoints = -1; if (itemState->isDisabled == 0) { memcpy(itemButtonUp, itemButtonUpBlank, sizeof(itemButtonUp)); memcpy(itemButtonDown, itemButtonDownBlank, sizeof(itemButtonDown)); if (itemState->isWeapon == 0) { int fid; if (proto_action_can_use_on(itemState->item->pid)) { // USE ON fid = art_id(OBJ_TYPE_INTERFACE, 294, 0, 0, 0); } else if (obj_action_can_use(itemState->item)) { // USE fid = art_id(OBJ_TYPE_INTERFACE, 292, 0, 0, 0); } else { fid = -1; } if (fid != -1) { CacheEntry* useTextFrmHandle; Art* useTextFrm = art_ptr_lock(fid, &useTextFrmHandle); if (useTextFrm != NULL) { int width = art_frame_width(useTextFrm, 0, 0); int height = art_frame_length(useTextFrm, 0, 0); unsigned char* data = art_frame_data(useTextFrm, 0, 0); trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * 7 + 181 - width, 188); dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 181 - width + 1, 5, 188, 59641); art_ptr_unlock(useTextFrmHandle); } actionPoints = item_mp_cost(obj_dude, itemState->primaryHitMode, false); } } else { int primaryFid = -1; int bullseyeFid = -1; int hitMode = -1; // NOTE: This value is decremented at 0x45FEAC, probably to build // jump table. switch (itemState->action) { case INTERFACE_ITEM_ACTION_PRIMARY_AIMING: bullseyeFid = art_id(OBJ_TYPE_INTERFACE, 288, 0, 0, 0); // FALLTHROUGH case INTERFACE_ITEM_ACTION_PRIMARY: hitMode = itemState->primaryHitMode; break; case INTERFACE_ITEM_ACTION_SECONDARY_AIMING: bullseyeFid = art_id(OBJ_TYPE_INTERFACE, 288, 0, 0, 0); // FALLTHROUGH case INTERFACE_ITEM_ACTION_SECONDARY: hitMode = itemState->secondaryHitMode; break; case INTERFACE_ITEM_ACTION_RELOAD: actionPoints = item_mp_cost(obj_dude, itemCurrentItem == HAND_LEFT ? HIT_MODE_LEFT_WEAPON_RELOAD : HIT_MODE_RIGHT_WEAPON_RELOAD, false); primaryFid = art_id(OBJ_TYPE_INTERFACE, 291, 0, 0, 0); break; } if (bullseyeFid != -1) { CacheEntry* bullseyeFrmHandle; Art* bullseyeFrm = art_ptr_lock(bullseyeFid, &bullseyeFrmHandle); if (bullseyeFrm != NULL) { int width = art_frame_width(bullseyeFrm, 0, 0); int height = art_frame_length(bullseyeFrm, 0, 0); unsigned char* data = art_frame_data(bullseyeFrm, 0, 0); trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * (60 - height) + (181 - width), 188); int v9 = 60 - height - 2; if (v9 < 0) { v9 = 0; height -= 2; } dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 181 - width + 1, v9, 188, 59641); art_ptr_unlock(bullseyeFrmHandle); } } if (hitMode != -1) { actionPoints = item_w_mp_cost(obj_dude, hitMode, bullseyeFid != -1); int id; int anim = item_w_anim(obj_dude, hitMode); switch (anim) { case ANIM_THROW_PUNCH: switch (hitMode) { case HIT_MODE_STRONG_PUNCH: id = 432; // strong punch break; case HIT_MODE_HAMMER_PUNCH: id = 425; // hammer punch break; case HIT_MODE_HAYMAKER: id = 428; // lightning punch break; case HIT_MODE_JAB: id = 421; // chop punch break; case HIT_MODE_PALM_STRIKE: id = 423; // dragon punch break; case HIT_MODE_PIERCING_STRIKE: id = 424; // force punch break; default: id = 42; // punch break; } break; case ANIM_KICK_LEG: switch (hitMode) { case HIT_MODE_STRONG_KICK: id = 430; // skick.frm - strong kick text break; case HIT_MODE_SNAP_KICK: id = 431; // snapkick.frm - snap kick text break; case HIT_MODE_POWER_KICK: id = 429; // cm_pwkck.frm - roundhouse kick text break; case HIT_MODE_HIP_KICK: id = 426; // hipk.frm - kip kick text break; case HIT_MODE_HOOK_KICK: id = 427; // cm_hookk.frm - jump kick text break; case HIT_MODE_PIERCING_KICK: // cm_prckk.frm - death blossom kick text id = 422; break; default: id = 41; // kick.frm - kick text break; } break; case ANIM_THROW_ANIM: id = 117; // throw break; case ANIM_THRUST_ANIM: id = 45; // thrust break; case ANIM_SWING_ANIM: id = 44; // swing break; case ANIM_FIRE_SINGLE: id = 43; // single break; case ANIM_FIRE_BURST: case ANIM_FIRE_CONTINUOUS: id = 40; // burst break; } primaryFid = art_id(OBJ_TYPE_INTERFACE, id, 0, 0, 0); } if (primaryFid != -1) { CacheEntry* primaryFrmHandle; Art* primaryFrm = art_ptr_lock(primaryFid, &primaryFrmHandle); if (primaryFrm != NULL) { int width = art_frame_width(primaryFrm, 0, 0); int height = art_frame_length(primaryFrm, 0, 0); unsigned char* data = art_frame_data(primaryFrm, 0, 0); trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * 7 + 181 - width, 188); dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 181 - width + 1, 5, 188, 59641); art_ptr_unlock(primaryFrmHandle); } } } } if (actionPoints >= 0 && actionPoints < 10) { // movement point text int fid = art_id(OBJ_TYPE_INTERFACE, 289, 0, 0, 0); CacheEntry* handle; Art* art = art_ptr_lock(fid, &handle); if (art != NULL) { int width = art_frame_width(art, 0, 0); int height = art_frame_length(art, 0, 0); unsigned char* data = art_frame_data(art, 0, 0); trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * (60 - height) + 7, 188); int v29 = 60 - height - 2; if (v29 < 0) { v29 = 0; height -= 2; } dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, 7 + 1, v29, 188, 59641); art_ptr_unlock(handle); int offset = width + 7; // movement point numbers - ten numbers 0 to 9, each 10 pixels wide. fid = art_id(OBJ_TYPE_INTERFACE, 290, 0, 0, 0); art = art_ptr_lock(fid, &handle); if (art != NULL) { width = art_frame_width(art, 0, 0); height = art_frame_length(art, 0, 0); data = art_frame_data(art, 0, 0); trans_buf_to_buf(data + actionPoints * 10, 10, height, width, itemButtonUp + 188 * (60 - height) + 7 + offset, 188); int v40 = 60 - height - 2; if (v40 < 0) { v40 = 0; height -= 2; } dark_trans_buf_to_buf(data + actionPoints * 10, 10, height, width, itemButtonDown, offset + 7 + 1, v40, 188, 59641); art_ptr_unlock(handle); } } } else { memcpy(itemButtonUp, itemButtonDisabled, sizeof(itemButtonUp)); memcpy(itemButtonDown, itemButtonDisabled, sizeof(itemButtonDown)); } if (itemState->itemFid != -1) { CacheEntry* itemFrmHandle; Art* itemFrm = art_ptr_lock(itemState->itemFid, &itemFrmHandle); if (itemFrm != NULL) { int width = art_frame_width(itemFrm, 0, 0); int height = art_frame_length(itemFrm, 0, 0); unsigned char* data = art_frame_data(itemFrm, 0, 0); int v46 = (188 - width) / 2; int v47 = (67 - height) / 2 - 2; trans_buf_to_buf(data, width, height, width, itemButtonUp + 188 * ((67 - height) / 2) + v46, 188); if (v47 < 0) { v47 = 0; height -= 2; } dark_trans_buf_to_buf(data, width, height, width, itemButtonDown, v46 + 1, v47, 188, 63571); art_ptr_unlock(itemFrmHandle); } } if (!insideInit) { intface_update_ammo_lights(); win_draw_rect(interfaceWindow, &itemButtonRect); if (itemState->isDisabled != 0) { win_disable_button(itemButton); } else { win_enable_button(itemButton); } } return 0; } // 0x460658 static int intface_redraw_items_callback(Object* a1, Object* a2) { intface_redraw_items(); return 0; } // 0x460660 static int intface_change_fid_callback(Object* a1, Object* a2) { intface_fid_is_changing = false; return 0; } // 0x46066C static void intface_change_fid_animate(int previousWeaponAnimationCode, int weaponAnimationCode) { intface_fid_is_changing = true; register_clear(obj_dude); register_begin(ANIMATION_REQUEST_RESERVED); register_object_light(obj_dude, 4, 0); if (previousWeaponAnimationCode != 0) { const char* sfx = gsnd_build_character_sfx_name(obj_dude, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(obj_dude, sfx, 0); register_object_animate(obj_dude, ANIM_PUT_AWAY, 0); } register_object_must_call(NULL, NULL, intface_redraw_items_callback, -1); Object* item = itemButtonItems[itemCurrentItem].item; if (item != NULL && item->lightDistance > 4) { register_object_light(obj_dude, item->lightDistance, 0); } if (weaponAnimationCode != 0) { register_object_take_out(obj_dude, weaponAnimationCode, -1); } else { int fid = art_id(OBJ_TYPE_CRITTER, obj_dude->fid & 0xFFF, ANIM_STAND, 0, obj_dude->rotation + 1); register_object_change_fid(obj_dude, fid, -1); } register_object_must_call(NULL, NULL, intface_change_fid_callback, -1); if (register_end() == -1) { return; } bool interfaceBarWasEnabled = intfaceEnabled; intface_disable(); gmouse_disable(0); gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH); while (intface_fid_is_changing) { if (game_user_wants_to_quit) { break; } get_input(); } gmouse_set_cursor(MOUSE_CURSOR_NONE); gmouse_enable(); if (interfaceBarWasEnabled) { intface_enable(); } } // 0x4607E0 static int intface_create_end_turn_button() { int fid; if (interfaceWindow == -1) { return -1; } if (!endWindowOpen) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 105, 0, 0, 0); endTurnButtonUp = art_ptr_lock_data(fid, 0, 0, &endTurnButtonUpKey); if (endTurnButtonUp == NULL) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 106, 0, 0, 0); endTurnButtonDown = art_ptr_lock_data(fid, 0, 0, &endTurnButtonDownKey); if (endTurnButtonDown == NULL) { return -1; } endTurnButton = win_register_button(interfaceWindow, 590, 43, 38, 22, -1, -1, -1, 32, endTurnButtonUp, endTurnButtonDown, NULL, 0); if (endTurnButton == -1) { return -1; } win_register_button_disable(endTurnButton, endTurnButtonUp, endTurnButtonUp, endTurnButtonUp); win_register_button_sound_func(endTurnButton, gsound_med_butt_press, gsound_med_butt_release); return 0; } // 0x4608C4 static int intface_destroy_end_turn_button() { if (interfaceWindow == -1) { return -1; } if (endTurnButton != -1) { win_delete_button(endTurnButton); endTurnButton = -1; } if (endTurnButtonDown) { art_ptr_unlock(endTurnButtonDownKey); endTurnButtonDownKey = NULL; endTurnButtonDown = NULL; } if (endTurnButtonUp) { art_ptr_unlock(endTurnButtonUpKey); endTurnButtonUpKey = NULL; endTurnButtonUp = NULL; } return 0; } // 0x460940 static int intface_create_end_combat_button() { int fid; if (interfaceWindow == -1) { return -1; } if (!endWindowOpen) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 107, 0, 0, 0); endCombatButtonUp = art_ptr_lock_data(fid, 0, 0, &endCombatButtonUpKey); if (endCombatButtonUp == NULL) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 108, 0, 0, 0); endCombatButtonDown = art_ptr_lock_data(fid, 0, 0, &endCombatButtonDownKey); if (endCombatButtonDown == NULL) { return -1; } endCombatButton = win_register_button(interfaceWindow, 590, 65, 38, 22, -1, -1, -1, 13, endCombatButtonUp, endCombatButtonDown, NULL, 0); if (endCombatButton == -1) { return -1; } win_register_button_disable(endCombatButton, endCombatButtonUp, endCombatButtonUp, endCombatButtonUp); win_register_button_sound_func(endCombatButton, gsound_med_butt_press, gsound_med_butt_release); return 0; } // 0x460A24 static int intface_destroy_end_combat_button() { if (interfaceWindow == -1) { return -1; } if (endCombatButton != -1) { win_delete_button(endCombatButton); endCombatButton = -1; } if (endCombatButtonDown != NULL) { art_ptr_unlock(endCombatButtonDownKey); endCombatButtonDownKey = NULL; endCombatButtonDown = NULL; } if (endCombatButtonUp != NULL) { art_ptr_unlock(endCombatButtonUpKey); endCombatButtonUpKey = NULL; endCombatButtonUp = NULL; } return 0; } // 0x460AA0 static void intface_draw_ammo_lights(int x, int ratio) { if ((ratio & 1) != 0) { ratio -= 1; } unsigned char* dest = interfaceBuffer + 640 * 26 + x; for (int index = 70; index > ratio; index--) { *dest = 14; dest += 640; } while (ratio > 0) { *dest = 196; dest += 640; *dest = 14; dest += 640; ratio -= 2; } if (!insideInit) { Rect rect; rect.ulx = x; rect.uly = 26; rect.lrx = x + 1; rect.lry = 26 + 70; win_draw_rect(interfaceWindow, &rect); } } // 0x460B20 static int intface_item_reload() { if (interfaceWindow == -1) { return -1; } bool v0 = false; while (item_w_try_reload(obj_dude, itemButtonItems[itemCurrentItem].item) != -1) { v0 = true; } intface_toggle_item_state(); intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); if (!v0) { return -1; } const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, itemButtonItems[itemCurrentItem].item, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL); gsound_play_sfx_file(sfx); return 0; } // Renders hit points. // // [delay] is an animation delay. // [previousValue] is only meaningful for animation. // [offset] = 0 - grey, 120 - yellow, 240 - red. // // 0x460BA0 static void intface_rotate_numbers(int x, int y, int previousValue, int value, int offset, int delay) { if (value > 999) { value = 999; } else if (value < -999) { value = -999; } unsigned char* numbers = numbersBuffer + offset; unsigned char* dest = interfaceBuffer + 640 * y; unsigned char* downSrc = numbers + 90; unsigned char* upSrc = numbers + 99; unsigned char* minusSrc = numbers + 108; unsigned char* plusSrc = numbers + 114; unsigned char* signDest = dest + x; unsigned char* hundredsDest = dest + x + 6; unsigned char* tensDest = dest + x + 6 + 9; unsigned char* onesDest = dest + x + 6 + 9 * 2; int normalizedSign; int normalizedValue; if (insideInit || delay == 0) { normalizedSign = value >= 0 ? 1 : -1; normalizedValue = abs(value); } else { normalizedSign = previousValue >= 0 ? 1 : -1; normalizedValue = previousValue; } int ones = normalizedValue % 10; int tens = (normalizedValue / 10) % 10; int hundreds = normalizedValue / 100; buf_to_buf(numbers + 9 * hundreds, 9, 17, 360, hundredsDest, 640); buf_to_buf(numbers + 9 * tens, 9, 17, 360, tensDest, 640); buf_to_buf(numbers + 9 * ones, 9, 17, 360, onesDest, 640); buf_to_buf(normalizedSign >= 0 ? plusSrc : minusSrc, 6, 17, 360, signDest, 640); if (!insideInit) { Rect numbersRect = { x, y, x + 33, y + 17 }; win_draw_rect(interfaceWindow, &numbersRect); if (delay != 0) { int change = value - previousValue >= 0 ? 1 : -1; int v14 = previousValue >= 0 ? 1 : -1; int v49 = change * v14; while (previousValue != value) { if ((hundreds | tens | ones) == 0) { v49 = 1; } buf_to_buf(upSrc, 9, 17, 360, onesDest, 640); mouse_info(); gmouse_bk_process(); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); ones += v49; if (ones > 9 || ones < 0) { buf_to_buf(upSrc, 9, 17, 360, tensDest, 640); mouse_info(); gmouse_bk_process(); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); tens += v49; ones -= 10 * v49; if (tens == 10 || tens == -1) { buf_to_buf(upSrc, 9, 17, 360, hundredsDest, 640); mouse_info(); gmouse_bk_process(); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); hundreds += v49; tens -= 10 * v49; if (hundreds == 10 || hundreds == -1) { hundreds -= 10 * v49; } buf_to_buf(downSrc, 9, 17, 360, hundredsDest, 640); mouse_info(); gmouse_bk_process(); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); } buf_to_buf(downSrc, 9, 17, 360, tensDest, 640); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); } buf_to_buf(downSrc, 9, 17, 360, onesDest, 640); mouse_info(); gmouse_bk_process(); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); previousValue += change; buf_to_buf(numbers + 9 * hundreds, 9, 17, 360, hundredsDest, 640); buf_to_buf(numbers + 9 * tens, 9, 17, 360, tensDest, 640); buf_to_buf(numbers + 9 * ones, 9, 17, 360, onesDest, 640); buf_to_buf(previousValue >= 0 ? plusSrc : minusSrc, 6, 17, 360, signDest, 640); mouse_info(); gmouse_bk_process(); block_for_tocks(delay); win_draw_rect(interfaceWindow, &numbersRect); } } } } // NOTE: Inlined. // // 0x461128 static int intface_fatal_error(int rc) { intface_exit(); return rc; } // 0x461134 static int construct_box_bar_win() { int oldFont = text_curr(); if (bar_window != -1) { return 0; } MessageList messageList; MessageListItem messageListItem; int rc = 0; if (!message_init(&messageList)) { rc = -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "intrface.msg"); if (rc != -1) { if (!message_load(&messageList, path)) { rc = -1; } } if (rc == -1) { debug_printf("\nINTRFACE: Error indicator box messages! **\n"); return -1; } CacheEntry* indicatorBoxFrmHandle; int width; int height; int indicatorBoxFid = art_id(OBJ_TYPE_INTERFACE, 126, 0, 0, 0); unsigned char* indicatorBoxFrmData = art_lock(indicatorBoxFid, &indicatorBoxFrmHandle, &width, &height); if (indicatorBoxFrmData == NULL) { debug_printf("\nINTRFACE: Error initializing indicator box graphics! **\n"); message_exit(&messageList); return -1; } for (int index = 0; index < INDICATOR_COUNT; index++) { IndicatorDescription* indicatorDescription = &(bbox[index]); indicatorDescription->data = (unsigned char*)mem_malloc(INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT); if (indicatorDescription->data == NULL) { debug_printf("\nINTRFACE: Error initializing indicator box graphics! **"); while (--index >= 0) { mem_free(bbox[index].data); } message_exit(&messageList); art_ptr_unlock(indicatorBoxFrmHandle); return -1; } } text_font(101); for (int index = 0; index < INDICATOR_COUNT; index++) { IndicatorDescription* indicator = &(bbox[index]); char text[1024]; strcpy(text, getmsg(&messageList, &messageListItem, indicator->title)); int color = indicator->isBad ? colorTable[31744] : colorTable[992]; memcpy(indicator->data, indicatorBoxFrmData, INDICATOR_BOX_WIDTH * INDICATOR_BOX_HEIGHT); // NOTE: For unknown reason it uses 24 as a height of the box to center // the title. One explanation is that these boxes were redesigned, but // this value was not changed. On the other hand 24 is // [INDICATOR_BOX_HEIGHT] + [INDICATOR_BOX_CONNECTOR_WIDTH]. Maybe just // a coincidence. I guess we'll never find out. int y = (24 - text_height()) / 2; int x = (INDICATOR_BOX_WIDTH - text_width(text)) / 2; text_to_buf(indicator->data + INDICATOR_BOX_WIDTH * y + x, text, INDICATOR_BOX_WIDTH, INDICATOR_BOX_WIDTH, color); } box_status_flag = true; refresh_box_bar_win(); message_exit(&messageList); art_ptr_unlock(indicatorBoxFrmHandle); text_font(oldFont); return 0; } // 0x461454 static void deconstruct_box_bar_win() { if (bar_window != -1) { win_delete(bar_window); bar_window = -1; } for (int index = 0; index < INDICATOR_COUNT; index++) { IndicatorDescription* indicatorBoxDescription = &(bbox[index]); if (indicatorBoxDescription->data != NULL) { mem_free(indicatorBoxDescription->data); indicatorBoxDescription->data = NULL; } } } // NOTE: Inlined. // // 0x4614A0 static void reset_box_bar_win() { if (bar_window != -1) { win_delete(bar_window); bar_window = -1; } box_status_flag = true; } // Updates indicator bar. // // 0x4614CC int refresh_box_bar_win() { if (interfaceWindow != -1 && box_status_flag && !intfaceHidden) { for (int index = 0; index < INDICATOR_SLOTS_COUNT; index++) { bboxslot[index] = -1; } int count = 0; if (is_pc_flag(DUDE_STATE_SNEAKING)) { if (add_bar_box(INDICATOR_SNEAK)) { ++count; } } if (is_pc_flag(DUDE_STATE_LEVEL_UP_AVAILABLE)) { if (add_bar_box(INDICATOR_LEVEL)) { ++count; } } if (is_pc_flag(DUDE_STATE_ADDICTED)) { if (add_bar_box(INDICATOR_ADDICT)) { ++count; } } if (critter_get_poison(obj_dude) > POISON_INDICATOR_THRESHOLD) { if (add_bar_box(INDICATOR_POISONED)) { ++count; } } if (critter_get_rads(obj_dude) > RADATION_INDICATOR_THRESHOLD) { if (add_bar_box(INDICATOR_RADIATED)) { ++count; } } if (count > 1) { qsort(bboxslot, count, sizeof(*bboxslot), bbox_comp); } if (bar_window != -1) { win_delete(bar_window); bar_window = -1; } if (count != 0) { bar_window = win_add(INDICATOR_BAR_X, INDICATOR_BAR_Y, (INDICATOR_BOX_WIDTH - INDICATOR_BOX_CONNECTOR_WIDTH) * count, INDICATOR_BOX_HEIGHT, colorTable[0], 0); draw_bboxes(count); win_draw(bar_window); } return count; } if (bar_window != -1) { win_delete(bar_window); bar_window = -1; } return 0; } // 0x461624 static int bbox_comp(const void* a, const void* b) { int indicatorBox1 = *(int*)a; int indicatorBox2 = *(int*)b; if (indicatorBox1 == indicatorBox2) { return 0; } else if (indicatorBox1 < indicatorBox2) { return -1; } else { return 1; } } // Renders indicator boxes into the indicator bar window. // // 0x461648 static void draw_bboxes(int count) { if (bar_window == -1) { return; } if (count == 0) { return; } int windowWidth = win_width(bar_window); unsigned char* windowBuffer = win_get_buf(bar_window); // The initial number of connections is 2 - one is first box to the screen // boundary, the other is female socket (initially empty). Every displayed // box adds one more connection (it is "plugged" into previous box and // exposes it's own empty female socket). int connections = 2; // The width of displayed indicator boxes as if there were no connections. int unconnectedIndicatorsWidth = 0; // The X offset to display next box. int x = 0; // The first box is connected to the screen boundary, so we have to clamp // male connectors on the left. int connectorWidthCompensation = INDICATOR_BOX_CONNECTOR_WIDTH; for (int index = 0; index < count; index++) { int indicator = bboxslot[index]; IndicatorDescription* indicatorDescription = &(bbox[indicator]); trans_buf_to_buf(indicatorDescription->data + connectorWidthCompensation, INDICATOR_BOX_WIDTH - connectorWidthCompensation, INDICATOR_BOX_HEIGHT, INDICATOR_BOX_WIDTH, windowBuffer + x, windowWidth); connectorWidthCompensation = 0; unconnectedIndicatorsWidth += INDICATOR_BOX_WIDTH; x = unconnectedIndicatorsWidth - INDICATOR_BOX_CONNECTOR_WIDTH * connections; connections++; } } // Adds indicator to the indicator bar. // // Returns `true` if indicator was added, or `false` if there is no available // space in the indicator bar. // // 0x4616F0 static bool add_bar_box(int indicator) { for (int index = 0; index < INDICATOR_SLOTS_COUNT; index++) { if (bboxslot[index] == -1) { bboxslot[index] = indicator; return true; } } debug_printf("\nINTRFACE: no free bar box slots!\n"); return false; } // 0x461740 bool enable_box_bar_win() { bool oldIsVisible = box_status_flag; box_status_flag = true; refresh_box_bar_win(); return oldIsVisible; } // 0x461760 bool disable_box_bar_win() { bool oldIsVisible = box_status_flag; box_status_flag = false; refresh_box_bar_win(); return oldIsVisible; } ================================================ FILE: src/game/intface.h ================================================ #ifndef FALLOUT_GAME_INTFACE_H_ #define FALLOUT_GAME_INTFACE_H_ #include #include "plib/db/db.h" #include "game/object_types.h" #define INTERFACE_BAR_WIDTH 640 #define INTERFACE_BAR_HEIGHT 100 typedef enum Hand { // Item1 (Punch) HAND_LEFT, // Item2 (Kick) HAND_RIGHT, HAND_COUNT, } Hand; typedef enum InterfaceItemAction { INTERFACE_ITEM_ACTION_DEFAULT = -1, INTERFACE_ITEM_ACTION_USE, INTERFACE_ITEM_ACTION_PRIMARY, INTERFACE_ITEM_ACTION_PRIMARY_AIMING, INTERFACE_ITEM_ACTION_SECONDARY, INTERFACE_ITEM_ACTION_SECONDARY_AIMING, INTERFACE_ITEM_ACTION_RELOAD, INTERFACE_ITEM_ACTION_COUNT, } InterfaceItemAction; extern int interfaceWindow; extern int bar_window; int intface_init(); void intface_reset(); void intface_exit(); int intface_load(File* stream); int intface_save(File* stream); void intface_hide(); void intface_show(); int intface_is_hidden(); void intface_enable(); void intface_disable(); bool intface_is_enabled(); void intface_redraw(); void intface_update_hit_points(bool animate); void intface_update_ac(bool animate); void intface_update_move_points(int actionPointsLeft, int bonusActionPoints); int intface_get_attack(int* hitMode, bool* aiming); int intface_update_items(bool animated, int leftItemAction, int rightItemAction); int intface_toggle_items(bool animated); int intface_get_item_states(int* leftItemAction, int* rightItemAction); int intface_toggle_item_state(); void intface_use_item(); int intface_is_item_right_hand(); int intface_get_current_item(Object** itemPtr); int intface_update_ammo_lights(); void intface_end_window_open(bool animated); void intface_end_window_close(bool animated); void intface_end_buttons_enable(); void intface_end_buttons_disable(); int refresh_box_bar_win(); bool enable_box_bar_win(); bool disable_box_bar_win(); #endif /* FALLOUT_GAME_INTFACE_H_ */ ================================================ FILE: src/game/inventry.c ================================================ #include "game/inventry.h" #include #include #include #include "game/actions.h" #include "game/anim.h" #include "game/art.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/bmpdlog.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "int/dialog.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gdialog.h" #include "game/gmouse.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/light.h" #include "game/map.h" #include "game/message.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/protinst.h" #include "game/roll.h" #include "game/reaction.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" #define INVENTORY_WINDOW_X 80 #define INVENTORY_WINDOW_Y 0 #define INVENTORY_TRADE_WINDOW_X 80 #define INVENTORY_TRADE_WINDOW_Y 290 #define INVENTORY_TRADE_WINDOW_WIDTH 480 #define INVENTORY_TRADE_WINDOW_HEIGHT 180 #define INVENTORY_LARGE_SLOT_WIDTH 90 #define INVENTORY_LARGE_SLOT_HEIGHT 61 #define INVENTORY_SLOT_WIDTH 64 #define INVENTORY_SLOT_HEIGHT 48 #define INVENTORY_LEFT_HAND_SLOT_X 154 #define INVENTORY_LEFT_HAND_SLOT_Y 286 #define INVENTORY_LEFT_HAND_SLOT_MAX_X (INVENTORY_LEFT_HAND_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_LEFT_HAND_SLOT_MAX_Y (INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) #define INVENTORY_RIGHT_HAND_SLOT_X 245 #define INVENTORY_RIGHT_HAND_SLOT_Y 286 #define INVENTORY_RIGHT_HAND_SLOT_MAX_X (INVENTORY_RIGHT_HAND_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_RIGHT_HAND_SLOT_MAX_Y (INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) #define INVENTORY_ARMOR_SLOT_X 154 #define INVENTORY_ARMOR_SLOT_Y 183 #define INVENTORY_ARMOR_SLOT_MAX_X (INVENTORY_ARMOR_SLOT_X + INVENTORY_LARGE_SLOT_WIDTH) #define INVENTORY_ARMOR_SLOT_MAX_Y (INVENTORY_ARMOR_SLOT_Y + INVENTORY_LARGE_SLOT_HEIGHT) #define INVENTORY_TRADE_LEFT_SCROLLER_X 29 #define INVENTORY_TRADE_RIGHT_SCROLLER_X 395 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_X 165 #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X 250 #define INVENTORY_TRADE_OUTER_SCROLLER_Y 35 #define INVENTORY_TRADE_INNER_SCROLLER_Y 20 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X 165 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X 0 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X 250 #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X 395 #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y 10 #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_MAX_X (INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_LOOT_LEFT_SCROLLER_X 176 #define INVENTORY_LOOT_LEFT_SCROLLER_Y 37 #define INVENTORY_LOOT_LEFT_SCROLLER_MAX_X (INVENTORY_LOOT_LEFT_SCROLLER_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_LOOT_RIGHT_SCROLLER_X 297 #define INVENTORY_LOOT_RIGHT_SCROLLER_Y 37 #define INVENTORY_LOOT_RIGHT_SCROLLER_MAX_X (INVENTORY_LOOT_RIGHT_SCROLLER_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_SCROLLER_X 44 #define INVENTORY_SCROLLER_Y 35 #define INVENTORY_SCROLLER_MAX_X (INVENTORY_SCROLLER_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_BODY_VIEW_WIDTH 60 #define INVENTORY_BODY_VIEW_HEIGHT 100 #define INVENTORY_PC_BODY_VIEW_X 176 #define INVENTORY_PC_BODY_VIEW_Y 37 #define INVENTORY_PC_BODY_VIEW_MAX_X (INVENTORY_PC_BODY_VIEW_X + INVENTORY_BODY_VIEW_WIDTH) #define INVENTORY_PC_BODY_VIEW_MAX_Y (INVENTORY_PC_BODY_VIEW_Y + INVENTORY_BODY_VIEW_HEIGHT) #define INVENTORY_LOOT_RIGHT_BODY_VIEW_X 422 #define INVENTORY_LOOT_RIGHT_BODY_VIEW_Y 35 #define INVENTORY_LOOT_LEFT_BODY_VIEW_X 44 #define INVENTORY_LOOT_LEFT_BODY_VIEW_Y 35 // NOTE: CE uses relative coordinates for hit testing for which coordinates // above is enough. However RE requires separate sets of coordinates as it // performs hit tests in screen coordinates. #define INVENTORY_LEFT_HAND_SLOT_ABS_X (INVENTORY_WINDOW_X + INVENTORY_LEFT_HAND_SLOT_X) #define INVENTORY_LEFT_HAND_SLOT_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_LEFT_HAND_SLOT_Y) #define INVENTORY_LEFT_HAND_SLOT_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_LEFT_HAND_SLOT_MAX_X) #define INVENTORY_LEFT_HAND_SLOT_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_LEFT_HAND_SLOT_MAX_Y) #define INVENTORY_RIGHT_HAND_SLOT_ABS_X (INVENTORY_WINDOW_X + INVENTORY_RIGHT_HAND_SLOT_X) #define INVENTORY_RIGHT_HAND_SLOT_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_RIGHT_HAND_SLOT_Y) #define INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_RIGHT_HAND_SLOT_MAX_X) #define INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_RIGHT_HAND_SLOT_MAX_Y) #define INVENTORY_ARMOR_SLOT_ABS_X (INVENTORY_WINDOW_X + INVENTORY_ARMOR_SLOT_X) #define INVENTORY_ARMOR_SLOT_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_ARMOR_SLOT_Y) #define INVENTORY_ARMOR_SLOT_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_ARMOR_SLOT_MAX_X) #define INVENTORY_ARMOR_SLOT_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_ARMOR_SLOT_MAX_Y) #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X) #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_Y) #define INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_Y) #define INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_Y) #define INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X) #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_Y (INVENTORY_TRADE_WINDOW_Y + 10 + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_Y) #define INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_MAX_X (INVENTORY_TRADE_WINDOW_X + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_X + INVENTORY_SLOT_WIDTH) #define INVENTORY_LOOT_LEFT_SCROLLER_ABS_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_LEFT_SCROLLER_X) #define INVENTORY_LOOT_LEFT_SCROLLER_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_LOOT_LEFT_SCROLLER_Y) #define INVENTORY_LOOT_LEFT_SCROLLER_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_LEFT_SCROLLER_MAX_X) #define INVENTORY_LOOT_RIGHT_SCROLLER_ABS_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_RIGHT_SCROLLER_X) #define INVENTORY_LOOT_RIGHT_SCROLLER_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_LOOT_RIGHT_SCROLLER_Y) #define INVENTORY_LOOT_RIGHT_SCROLLER_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_LOOT_RIGHT_SCROLLER_MAX_X) #define INVENTORY_SCROLLER_ABS_X (INVENTORY_WINDOW_X + INVENTORY_SCROLLER_X) #define INVENTORY_SCROLLER_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_SCROLLER_Y) #define INVENTORY_SCROLLER_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_SCROLLER_MAX_X) #define INVENTORY_PC_BODY_VIEW_ABS_X (INVENTORY_WINDOW_X + INVENTORY_PC_BODY_VIEW_X) #define INVENTORY_PC_BODY_VIEW_ABS_Y (INVENTORY_WINDOW_Y + INVENTORY_PC_BODY_VIEW_Y) #define INVENTORY_PC_BODY_VIEW_ABS_MAX_X (INVENTORY_WINDOW_X + INVENTORY_PC_BODY_VIEW_MAX_X) #define INVENTORY_PC_BODY_VIEW_ABS_MAX_Y (INVENTORY_WINDOW_Y + INVENTORY_PC_BODY_VIEW_MAX_Y) #define INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY (1000U / ROTATION_COUNT) typedef void(InventoryPrintItemDescriptionHandler)(char* string); typedef enum InventoryArrowFrm { INVENTORY_ARROW_FRM_LEFT_ARROW_UP, INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN, INVENTORY_ARROW_FRM_RIGHT_ARROW_UP, INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN, INVENTORY_ARROW_FRM_COUNT, } InventoryArrowFrm; typedef struct InventoryWindowConfiguration { int field_0; // artId int width; int height; int x; int y; } InventoryWindowDescription; typedef struct InventoryCursorData { Art* frm; unsigned char* frmData; int width; int height; int offsetX; int offsetY; CacheEntry* frmHandle; } InventoryCursorData; static int inventry_msg_load(); static int inventry_msg_unload(); static void display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool a5); static void inven_update_lighting(Object* a1); static int barter_compute_value(Object* a1, Object* a2); static int barter_attempt_transaction(Object* a1, Object* a2, Object* a3, Object* a4); static void barter_move_inventory(Object* a1, int quantity, int a3, int a4, Object* a5, Object* a6, bool a7); static void barter_move_from_table_inventory(Object* a1, int quantity, int a3, Object* a4, Object* a5, bool a6); static void display_table_inventories(int win, Object* a2, Object* a3, int a4); static int do_move_timer(int inventoryWindowType, Object* item, int a3); static int setup_move_timer_win(int inventoryWindowType, Object* item); static int exit_move_timer_win(int inventoryWindowType); // The number of items to show in scroller. // // 0x519054 static int inven_cur_disp = 6; // 0x519058 static Object* inven_dude = NULL; // Probably fid of armor to display in inventory dialog. // // 0x51905C static int inven_pid = -1; // 0x519060 static bool inven_is_initialized = false; // 0x519064 static int inven_display_msg_line = 1; // 0x519068 static InventoryWindowDescription iscr_data[INVENTORY_WINDOW_TYPE_COUNT] = { { 48, 499, 377, 80, 0 }, { 113, 292, 376, 80, 0 }, { 114, 537, 376, 80, 0 }, { 111, 480, 180, 80, 290 }, { 305, 259, 162, 140, 80 }, { 305, 259, 162, 140, 80 }, }; // 0x5190E0 static bool dropped_explosive = false; // 0x5190E4 static int inven_scroll_up_bid = -1; // 0x5190E8 static int inven_scroll_dn_bid = -1; // 0x5190EC static int loot_scroll_up_bid = -1; // 0x5190F0 static int loot_scroll_dn_bid = -1; // 0x59E79C static CacheEntry* mt_key[8]; // 0x59E7BC CacheEntry* ikey[OFF_59E7BC_COUNT]; // 0x59E7EC static int target_stack_offset[10]; // inventory.msg // // 0x59E814 static MessageList inventry_message_file; // 0x59E81C static Object* target_stack[10]; // 0x59E844 static int stack_offset[10]; // 0x59E86C static Object* stack[10]; // 0x59E894 static int mt_wid; // 0x59E898 static int barter_mod; // 0x59E89C static int btable_offset; // 0x59E8A0 static int ptable_offset; // 0x59E8A4 static Inventory* ptable_pud; // 0x59E8A8 static InventoryCursorData imdata[INVENTORY_WINDOW_CURSOR_COUNT]; // 0x59E934 static Object* ptable; // 0x59E938 static InventoryPrintItemDescriptionHandler* display_msg; // 0x59E93C static int im_value; // 0x59E940 static int immode; // 0x59E944 static Object* btable; // 0x59E948 static int target_curr_stack; // 0x59E94C static Inventory* btable_pud; // 0x59E950 static bool inven_ui_was_disabled; // 0x59E954 static Object* i_worn; // 0x59E958 static Object* i_lhand; // Rotating character's fid. // // 0x59E95C static int i_fid; // 0x59E960 static Inventory* pud; // 0x59E964 static int i_wid; // item2 // 0x59E968 static Object* i_rhand; // 0x59E96C static int curr_stack; // 0x59E970 static int i_wid_max_y; // 0x59E974 static int i_wid_max_x; // 0x59E978 static Inventory* target_pud; // 0x59E97C static int barter_back_win; // NOTE: Unused. // // 0x46E718 void inven_set_dude(Object* obj, int pid) { inven_dude = obj; inven_pid = pid; } // 0x46E724 void inven_reset_dude() { inven_dude = obj_dude; inven_pid = 0x1000000; } // 0x46E73C static int inventry_msg_load() { char path[MAX_PATH]; if (!message_init(&inventry_message_file)) return -1; sprintf(path, "%s%s", msg_path, "inventry.msg"); if (!message_load(&inventry_message_file, path)) return -1; return 0; } // 0x46E7A0 static int inventry_msg_unload() { message_exit(&inventry_message_file); return 0; } // 0x46E7B0 void handle_inventory() { if (isInCombat()) { if (combat_whose_turn() != inven_dude) { return; } } if (inven_init() == -1) { return; } if (isInCombat()) { if (inven_dude == obj_dude) { int actionPointsRequired = 4 - 2 * perk_level(inven_dude, PERK_QUICK_POCKETS); if (actionPointsRequired > 0 && actionPointsRequired > obj_dude->data.critter.combat.ap) { // You don't have enough action points to use inventory MessageListItem messageListItem; messageListItem.num = 19; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } // NOTE: Uninline. inven_exit(); return; } if (actionPointsRequired > 0) { if (actionPointsRequired > obj_dude->data.critter.combat.ap) { obj_dude->data.critter.combat.ap = 0; } else { obj_dude->data.critter.combat.ap -= actionPointsRequired; } intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } } } Object* oldArmor = inven_worn(inven_dude); bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_NORMAL); register_clear(inven_dude); display_stats(); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); for (;;) { int keyCode = get_input(); if (keyCode == KEY_ESCAPE) { break; } if (game_user_wants_to_quit != 0) { break; } display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); if (game_state() == GAME_STATE_5) { break; } if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X) { game_quit_with_confirm(); } else if (keyCode == KEY_HOME) { stack_offset[curr_stack] = 0; display_inventory(0, -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == KEY_ARROW_UP) { if (stack_offset[curr_stack] > 0) { stack_offset[curr_stack] -= 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } } else if (keyCode == KEY_PAGE_UP) { stack_offset[curr_stack] -= inven_cur_disp; if (stack_offset[curr_stack] < 0) { stack_offset[curr_stack] = 0; } display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == KEY_END) { stack_offset[curr_stack] = pud->length - inven_cur_disp; if (stack_offset[curr_stack] < 0) { stack_offset[curr_stack] = 0; } display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == KEY_ARROW_DOWN) { if (inven_cur_disp + stack_offset[curr_stack] < pud->length) { stack_offset[curr_stack] += 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } } else if (keyCode == KEY_PAGE_DOWN) { int v12 = inven_cur_disp + stack_offset[curr_stack]; int v13 = v12 + inven_cur_disp; stack_offset[curr_stack] = v12; int v14 = pud->length; if (v13 >= pud->length) { int v15 = v14 - inven_cur_disp; stack_offset[curr_stack] = v14 - inven_cur_disp; if (v15 < 0) { stack_offset[curr_stack] = 0; } } display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_NORMAL); } else if (keyCode == 2500) { container_exit(keyCode, INVENTORY_WINDOW_TYPE_NORMAL); } else { if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (immode == INVENTORY_WINDOW_CURSOR_HAND) { inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW); } else if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); display_stats(); win_draw(i_wid); } } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode <= 1008) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_NORMAL); } else { inven_pickup(keyCode, stack_offset[curr_stack]); } } } } } inven_dude = stack[0]; adjust_fid(); if (inven_dude == obj_dude) { Rect rect; obj_change_fid(inven_dude, i_fid, &rect); tile_refresh_rect(&rect, inven_dude->elevation); } Object* newArmor = inven_worn(inven_dude); if (inven_dude == obj_dude) { if (oldArmor != newArmor) { intface_update_ac(true); } } exit_inventory(isoWasEnabled); // NOTE: Uninline. inven_exit(); if (inven_dude == obj_dude) { intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } // 0x46EC90 bool setup_inventory(int inventoryWindowType) { dropped_explosive = 0; curr_stack = 0; stack_offset[0] = 0; inven_cur_disp = 6; pud = &(inven_dude->data.inventory); stack[0] = inven_dude; if (inventoryWindowType <= INVENTORY_WINDOW_TYPE_LOOT) { InventoryWindowDescription* windowDescription = &(iscr_data[inventoryWindowType]); int inventoryWindowX = INVENTORY_WINDOW_X; int inventoryWindowY = INVENTORY_WINDOW_Y; i_wid = win_add(inventoryWindowX, inventoryWindowY, windowDescription->width, windowDescription->height, 257, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); i_wid_max_x = windowDescription->width + inventoryWindowX; i_wid_max_y = windowDescription->height + inventoryWindowY; unsigned char* dest = win_get_buf(i_wid); int backgroundFid = art_id(OBJ_TYPE_INTERFACE, windowDescription->field_0, 0, 0, 0); CacheEntry* backgroundFrmHandle; unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { buf_to_buf(backgroundFrmData, windowDescription->width, windowDescription->height, windowDescription->width, dest, windowDescription->width); art_ptr_unlock(backgroundFrmHandle); } display_msg = display_print; } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { if (barter_back_win == -1) { exit(1); } inven_cur_disp = 3; int tradeWindowX = INVENTORY_TRADE_WINDOW_X; int tradeWindowY = INVENTORY_TRADE_WINDOW_Y; i_wid = win_add(tradeWindowX, tradeWindowY, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, 257, 0); i_wid_max_x = tradeWindowX + INVENTORY_TRADE_WINDOW_WIDTH; i_wid_max_y = tradeWindowY + INVENTORY_TRADE_WINDOW_HEIGHT; unsigned char* dest = win_get_buf(i_wid); unsigned char* src = win_get_buf(barter_back_win); buf_to_buf(src + INVENTORY_TRADE_WINDOW_X, INVENTORY_TRADE_WINDOW_WIDTH, INVENTORY_TRADE_WINDOW_HEIGHT, scr_size.lrx - scr_size.ulx + 1, dest, INVENTORY_TRADE_WINDOW_WIDTH); display_msg = gdialogDisplayMsg; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { // Create invsibile buttons representing character's inventory item // slots. for (int index = 0; index < inven_cur_disp; index++) { int btn = win_register_button(i_wid, INVENTORY_LOOT_LEFT_SCROLLER_X, INVENTORY_SLOT_HEIGHT * (inven_cur_disp - index - 1) + INVENTORY_LOOT_LEFT_SCROLLER_Y, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 999 + inven_cur_disp - index, -1, 999 + inven_cur_disp - index, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } } int eventCode = 2005; int y = INVENTORY_SLOT_HEIGHT * 5 + INVENTORY_LOOT_LEFT_SCROLLER_Y; // Create invisible buttons representing container's inventory item // slots. For unknown reason it loops backwards and it's size is // hardcoded at 6 items. // // Original code is slightly different. It loops until y reaches -11, // which is a bit awkward for a loop. Probably result of some // optimization. for (int index = 0; index < 6; index++) { int btn = win_register_button(i_wid, INVENTORY_LOOT_RIGHT_SCROLLER_X, y, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, eventCode, -1, eventCode, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } eventCode -= 1; y -= INVENTORY_SLOT_HEIGHT; } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { int y1 = INVENTORY_TRADE_OUTER_SCROLLER_Y; int y2 = INVENTORY_TRADE_INNER_SCROLLER_Y; for (int index = 0; index < inven_cur_disp; index++) { int btn; // Invsibile button representing left inventory slot. btn = win_register_button(i_wid, INVENTORY_TRADE_LEFT_SCROLLER_X, y1, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 1000 + index, -1, 1000 + index, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } // Invisible button representing right inventory slot. btn = win_register_button(i_wid, INVENTORY_TRADE_RIGHT_SCROLLER_X, y1, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2000 + index, -1, 2000 + index, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } // Invisible button representing left suggested slot. btn = win_register_button(i_wid, INVENTORY_TRADE_INNER_LEFT_SCROLLER_X, y2, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2300 + index, -1, 2300 + index, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } // Invisible button representing right suggested slot. btn = win_register_button(i_wid, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_X, y2, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 2400 + index, -1, 2400 + index, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } y1 += INVENTORY_SLOT_HEIGHT; y2 += INVENTORY_SLOT_HEIGHT; } } else { // Create invisible buttons representing item slots. for (int index = 0; index < inven_cur_disp; index++) { int btn = win_register_button(i_wid, INVENTORY_SCROLLER_X, INVENTORY_SLOT_HEIGHT * (inven_cur_disp - index - 1) + INVENTORY_SCROLLER_Y, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 999 + inven_cur_disp - index, -1, 999 + inven_cur_disp - index, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { int btn; // Item2 slot btn = win_register_button(i_wid, INVENTORY_RIGHT_HAND_SLOT_X, INVENTORY_RIGHT_HAND_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 1006, -1, 1006, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } // Item1 slot btn = win_register_button(i_wid, INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LEFT_HAND_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 1007, -1, 1007, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } // Armor slot btn = win_register_button(i_wid, INVENTORY_ARMOR_SLOT_X, INVENTORY_ARMOR_SLOT_Y, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 1008, -1, 1008, -1, NULL, NULL, NULL, 0); if (btn != -1) { win_register_button_func(btn, inven_hover_on, inven_hover_off, NULL, NULL); } } memset(ikey, 0, sizeof(ikey)); int fid; int btn; unsigned char* buttonUpData; unsigned char* buttonDownData; unsigned char* buttonDisabledData; fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[0])); fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[1])); if (buttonUpData != NULL && buttonDownData != NULL) { btn = -1; switch (inventoryWindowType) { case INVENTORY_WINDOW_TYPE_NORMAL: // Done button btn = win_register_button(i_wid, 437, 329, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); break; case INVENTORY_WINDOW_TYPE_USE_ITEM_ON: // Cancel button btn = win_register_button(i_wid, 233, 328, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); break; case INVENTORY_WINDOW_TYPE_LOOT: // Done button btn = win_register_button(i_wid, 476, 331, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); break; } if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { // TODO: Figure out why it building fid in chain. // Large arrow up (normal). fid = art_id(OBJ_TYPE_INTERFACE, 100, 0, 0, 0); fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[2])); // Large arrow up (pressed). fid = art_id(OBJ_TYPE_INTERFACE, 101, 0, 0, 0); fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[3])); if (buttonUpData != NULL && buttonDownData != NULL) { // Left inventory up button. btn = win_register_button(i_wid, 109, 56, 23, 24, -1, -1, KEY_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // Right inventory up button. btn = win_register_button(i_wid, 342, 56, 23, 24, -1, -1, KEY_CTRL_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } } else { // Large up arrow (normal). fid = art_id(OBJ_TYPE_INTERFACE, 49, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[2])); // Large up arrow (pressed). fid = art_id(OBJ_TYPE_INTERFACE, 50, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[3])); // Large up arrow (disabled). fid = art_id(OBJ_TYPE_INTERFACE, 53, 0, 0, 0); buttonDisabledData = art_ptr_lock_data(fid, 0, 0, &(ikey[4])); if (buttonUpData != NULL && buttonDownData != NULL && buttonDisabledData != NULL) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) { // Left inventory up button. inven_scroll_up_bid = win_register_button(i_wid, 128, 39, 22, 23, -1, -1, KEY_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (inven_scroll_up_bid != -1) { win_register_button_disable(inven_scroll_up_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData); win_register_button_sound_func(inven_scroll_up_bid, gsound_red_butt_press, gsound_red_butt_release); win_disable_button(inven_scroll_up_bid); } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { // Right inventory up button. loot_scroll_up_bid = win_register_button(i_wid, 379, 39, 22, 23, -1, -1, KEY_CTRL_ARROW_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (loot_scroll_up_bid != -1) { win_register_button_disable(loot_scroll_up_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData); win_register_button_sound_func(loot_scroll_up_bid, gsound_red_butt_press, gsound_red_butt_release); win_disable_button(loot_scroll_up_bid); } } } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { // Large dialog down button (normal) fid = art_id(OBJ_TYPE_INTERFACE, 93, 0, 0, 0); fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[5])); // Dialog down button (pressed) fid = art_id(OBJ_TYPE_INTERFACE, 94, 0, 0, 0); fid = art_id(OBJ_TYPE_INTERFACE, fid, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[6])); if (buttonUpData != NULL && buttonDownData != NULL) { // Left inventory down button. btn = win_register_button(i_wid, 109, 82, 24, 25, -1, -1, KEY_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // Right inventory down button btn = win_register_button(i_wid, 342, 82, 24, 25, -1, -1, KEY_CTRL_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // Invisible button representing left character. win_register_button(barter_back_win, 15, 25, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2500, -1, NULL, NULL, NULL, 0); // Invisible button representing right character. win_register_button(barter_back_win, 560, 25, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2501, -1, NULL, NULL, NULL, 0); } } else { // Large arrow down (normal). fid = art_id(OBJ_TYPE_INTERFACE, 51, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[5])); // Large arrow down (pressed). fid = art_id(OBJ_TYPE_INTERFACE, 52, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[6])); // Large arrow down (disabled). fid = art_id(OBJ_TYPE_INTERFACE, 54, 0, 0, 0); buttonDisabledData = art_ptr_lock_data(fid, 0, 0, &(ikey[7])); if (buttonUpData != NULL && buttonDownData != NULL && buttonDisabledData != NULL) { // Left inventory down button. inven_scroll_dn_bid = win_register_button(i_wid, 128, 62, 22, 23, -1, -1, KEY_ARROW_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); win_register_button_sound_func(inven_scroll_dn_bid, gsound_red_butt_press, gsound_red_butt_release); win_register_button_disable(inven_scroll_dn_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData); win_disable_button(inven_scroll_dn_bid); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { // Invisible button representing left character. win_register_button(i_wid, INVENTORY_LOOT_LEFT_BODY_VIEW_X, INVENTORY_LOOT_LEFT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2500, -1, NULL, NULL, NULL, 0); // Right inventory down button. loot_scroll_dn_bid = win_register_button(i_wid, 379, 62, 22, 23, -1, -1, KEY_CTRL_ARROW_DOWN, -1, buttonUpData, buttonDownData, 0, 0); if (loot_scroll_dn_bid != -1) { win_register_button_sound_func(loot_scroll_dn_bid, gsound_red_butt_press, gsound_red_butt_release); win_register_button_disable(loot_scroll_dn_bid, buttonDisabledData, buttonDisabledData, buttonDisabledData); win_disable_button(loot_scroll_dn_bid); } // Invisible button representing right character. win_register_button(i_wid, INVENTORY_LOOT_RIGHT_BODY_VIEW_X, INVENTORY_LOOT_RIGHT_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2501, -1, NULL, NULL, NULL, 0); } else { // Invisible button representing character (in inventory and use on dialogs). win_register_button(i_wid, INVENTORY_PC_BODY_VIEW_X, INVENTORY_PC_BODY_VIEW_Y, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, -1, -1, 2500, -1, NULL, NULL, NULL, 0); } } } if (inventoryWindowType != INVENTORY_WINDOW_TYPE_TRADE) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { if (!gIsSteal) { // Take all button (normal) fid = art_id(OBJ_TYPE_INTERFACE, 436, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[8])); // Take all button (pressed) fid = art_id(OBJ_TYPE_INTERFACE, 437, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[9])); if (buttonUpData != NULL && buttonDownData != NULL) { // Take all button. btn = win_register_button(i_wid, 432, 204, 39, 41, -1, -1, KEY_UPPERCASE_A, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } } } } else { // Inventory button up (normal) fid = art_id(OBJ_TYPE_INTERFACE, 49, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[8])); // Inventory button up (pressed) fid = art_id(OBJ_TYPE_INTERFACE, 50, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[9])); if (buttonUpData != NULL && buttonDownData != NULL) { // Left offered inventory up button. btn = win_register_button(i_wid, 128, 113, 22, 23, -1, -1, KEY_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // Right offered inventory up button. btn = win_register_button(i_wid, 333, 113, 22, 23, -1, -1, KEY_CTRL_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } // Inventory button down (normal) fid = art_id(OBJ_TYPE_INTERFACE, 51, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(ikey[8])); // Inventory button down (pressed). fid = art_id(OBJ_TYPE_INTERFACE, 52, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(ikey[9])); if (buttonUpData != NULL && buttonDownData != NULL) { // Left offered inventory down button. btn = win_register_button(i_wid, 128, 136, 22, 23, -1, -1, KEY_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // Right offered inventory down button. btn = win_register_button(i_wid, 333, 136, 22, 23, -1, -1, KEY_CTRL_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } } i_rhand = NULL; i_worn = NULL; i_lhand = NULL; for (int index = 0; index < pud->length; index++) { InventoryItem* inventoryItem = &(pud->items[index]); Object* item = inventoryItem->item; if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) { if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { i_rhand = item; } i_lhand = item; } else if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { i_rhand = item; } else if ((item->flags & OBJECT_WORN) != 0) { i_worn = item; } } if (i_lhand != NULL) { item_remove_mult(inven_dude, i_lhand, 1); } if (i_rhand != NULL && i_rhand != i_lhand) { item_remove_mult(inven_dude, i_rhand, 1); } if (i_worn != NULL) { item_remove_mult(inven_dude, i_worn, 1); } adjust_fid(); bool isoWasEnabled = map_disable_bk_processes(); gmouse_disable(0); return isoWasEnabled; } // 0x46FBD8 void exit_inventory(bool shouldEnableIso) { inven_dude = stack[0]; if (i_lhand != NULL) { i_lhand->flags |= OBJECT_IN_LEFT_HAND; if (i_lhand == i_rhand) { i_lhand->flags |= OBJECT_IN_RIGHT_HAND; } item_add_force(inven_dude, i_lhand, 1); } if (i_rhand != NULL && i_rhand != i_lhand) { i_rhand->flags |= OBJECT_IN_RIGHT_HAND; item_add_force(inven_dude, i_rhand, 1); } if (i_worn != NULL) { i_worn->flags |= OBJECT_WORN; item_add_force(inven_dude, i_worn, 1); } i_rhand = NULL; i_worn = NULL; i_lhand = NULL; for (int index = 0; index < OFF_59E7BC_COUNT; index++) { art_ptr_unlock(ikey[index]); } if (shouldEnableIso) { map_enable_bk_processes(); } win_delete(i_wid); gmouse_enable(); if (dropped_explosive) { Attack v1; combat_ctd_init(&v1, obj_dude, NULL, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); v1.attackerFlags = DAM_HIT; v1.tile = obj_dude->tile; compute_explosion_on_extras(&v1, 0, 0, 1); Object* v2 = NULL; for (int index = 0; index < v1.extrasLength; index++) { Object* critter = v1.extras[index]; if (critter != obj_dude && critter->data.critter.combat.team != obj_dude->data.critter.combat.team && stat_result(critter, STAT_PERCEPTION, 0, NULL) >= ROLL_SUCCESS) { critter_set_who_hit_me(critter, obj_dude); if (v2 == NULL) { v2 = critter; } } } if (v2 != NULL) { if (!isInCombat()) { STRUCT_664980 v3; v3.attacker = v2; v3.defender = obj_dude; v3.actionPointsBonus = 0; v3.accuracyBonus = 0; v3.damageBonus = 0; v3.minDamage = 0; v3.maxDamage = INT_MAX; v3.field_1C = 0; scripts_request_combat(&v3); } } dropped_explosive = false; } } // 0x46FDF4 void display_inventory(int a1, int a2, int inventoryWindowType) { unsigned char* windowBuffer = win_get_buf(i_wid); int pitch; int v49 = 0; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { pitch = 499; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); CacheEntry* backgroundFrmHandle; unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { // Clear scroll view background. buf_to_buf(backgroundFrmData + pitch * 35 + 44, INVENTORY_SLOT_WIDTH, inven_cur_disp * INVENTORY_SLOT_HEIGHT, pitch, windowBuffer + pitch * 35 + 44, pitch); // Clear armor button background. buf_to_buf(backgroundFrmData + pitch * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, pitch); if (i_lhand != NULL && i_lhand == i_rhand) { // Clear item1. int itemBackgroundFid = art_id(OBJ_TYPE_INTERFACE, 32, 0, 0, 0); CacheEntry* itemBackgroundFrmHandle; Art* itemBackgroundFrm = art_ptr_lock(itemBackgroundFid, &itemBackgroundFrmHandle); if (itemBackgroundFrm != NULL) { unsigned char* data = art_frame_data(itemBackgroundFrm, 0, 0); int width = art_frame_width(itemBackgroundFrm, 0, 0); int height = art_frame_length(itemBackgroundFrm, 0, 0); buf_to_buf(data, width, height, width, windowBuffer + pitch * 284 + 152, pitch); art_ptr_unlock(itemBackgroundFrmHandle); } } else { // Clear both items in one go. buf_to_buf(backgroundFrmData + pitch * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH * 2, INVENTORY_LARGE_SLOT_HEIGHT, pitch, windowBuffer + pitch * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, pitch); } art_ptr_unlock(backgroundFrmHandle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON) { pitch = 292; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 113, 0, 0, 0); CacheEntry* backgroundFrmHandle; unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { // Clear scroll view background. buf_to_buf(backgroundFrmData + pitch * 35 + 44, 64, inven_cur_disp * 48, pitch, windowBuffer + pitch * 35 + 44, pitch); art_ptr_unlock(backgroundFrmHandle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { pitch = 537; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); CacheEntry* backgroundFrmHandle; unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { // Clear scroll view background. buf_to_buf(backgroundFrmData + pitch * 37 + 176, 64, inven_cur_disp * 48, pitch, windowBuffer + pitch * 37 + 176, pitch); art_ptr_unlock(backgroundFrmHandle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { pitch = 480; windowBuffer = win_get_buf(i_wid); buf_to_buf(win_get_buf(barter_back_win) + 35 * (scr_size.lrx - scr_size.ulx + 1) + 100, 64, 48 * inven_cur_disp, scr_size.lrx - scr_size.ulx + 1, windowBuffer + pitch * 35 + 20, pitch); v49 = -20; } else { assert(false && "Should be unreachable"); } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL || inventoryWindowType == INVENTORY_WINDOW_TYPE_USE_ITEM_ON || inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { if (inven_scroll_up_bid != -1) { if (a1 <= 0) { win_disable_button(inven_scroll_up_bid); } else { win_enable_button(inven_scroll_up_bid); } } if (inven_scroll_dn_bid != -1) { if (pud->length - a1 <= inven_cur_disp) { win_disable_button(inven_scroll_dn_bid); } else { win_enable_button(inven_scroll_dn_bid); } } } int y = 0; for (int v19 = 0; v19 + a1 < pud->length && v19 < inven_cur_disp; v19 += 1) { int v21 = v19 + a1 + 1; int width; int offset; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { offset = pitch * (y + 39) + 26; width = 59; } else { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { offset = pitch * (y + 41) + 180; } else { offset = pitch * (y + 39) + 48; } width = 56; } InventoryItem* inventoryItem = &(pud->items[pud->length - v21]); int inventoryFid = item_inv_fid(inventoryItem->item); scale_art(inventoryFid, windowBuffer + offset, width, 40, pitch); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { offset = pitch * (y + 41) + 180 + v49; } else { offset = pitch * (y + 39) + 48 + v49; } display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, v19 == a2); y += 48; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { if (i_rhand != NULL) { int width = i_rhand == i_lhand ? INVENTORY_LARGE_SLOT_WIDTH * 2 : INVENTORY_LARGE_SLOT_WIDTH; int inventoryFid = item_inv_fid(i_rhand); scale_art(inventoryFid, windowBuffer + 499 * INVENTORY_RIGHT_HAND_SLOT_Y + INVENTORY_RIGHT_HAND_SLOT_X, width, INVENTORY_LARGE_SLOT_HEIGHT, 499); } if (i_lhand != NULL && i_lhand != i_rhand) { int inventoryFid = item_inv_fid(i_lhand); scale_art(inventoryFid, windowBuffer + 499 * INVENTORY_LEFT_HAND_SLOT_Y + INVENTORY_LEFT_HAND_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 499); } if (i_worn != NULL) { int inventoryFid = item_inv_fid(i_worn); scale_art(inventoryFid, windowBuffer + 499 * INVENTORY_ARMOR_SLOT_Y + INVENTORY_ARMOR_SLOT_X, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, 499); } } win_draw(i_wid); } // Render inventory item. // // [a1] is likely an index of the first visible item in the scrolling view. // [a2] is likely an index of selected item or moving item (it decreases displayed number of items in inner functions). // // 0x47036C void display_target_inventory(int a1, int a2, Inventory* inventory, int inventoryWindowType) { unsigned char* windowBuffer = win_get_buf(i_wid); int pitch; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { pitch = 537; int fid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); CacheEntry* handle; unsigned char* data = art_ptr_lock_data(fid, 0, 0, &handle); if (data != NULL) { buf_to_buf(data + 537 * 37 + 297, 64, 48 * inven_cur_disp, 537, windowBuffer + 537 * 37 + 297, 537); art_ptr_unlock(handle); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { pitch = 480; unsigned char* src = win_get_buf(barter_back_win); buf_to_buf(src + (scr_size.lrx - scr_size.ulx + 1) * 35 + 475, 64, 48 * inven_cur_disp, scr_size.lrx - scr_size.ulx + 1, windowBuffer + 480 * 35 + 395, 480); } else { assert(false && "Should be unreachable"); } int y = 0; for (int index = 0; index < inven_cur_disp; index++) { int v27 = a1 + index; if (v27 >= inventory->length) { break; } int offset; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { offset = pitch * (y + 41) + 301; } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { offset = pitch * (y + 39) + 397; } else { assert(false && "Should be unreachable"); } InventoryItem* inventoryItem = &(inventory->items[inventory->length - (v27 + 1)]); int inventoryFid = item_inv_fid(inventoryItem->item); scale_art(inventoryFid, windowBuffer + offset, 56, 40, pitch); display_inventory_info(inventoryItem->item, inventoryItem->quantity, windowBuffer + offset, pitch, index == a2); y += 48; } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { if (loot_scroll_up_bid != -1) { if (a1 <= 0) { win_disable_button(loot_scroll_up_bid); } else { win_enable_button(loot_scroll_up_bid); } } if (loot_scroll_dn_bid != -1) { if (inventory->length - a1 <= inven_cur_disp) { win_disable_button(loot_scroll_dn_bid); } else { win_enable_button(loot_scroll_dn_bid); } } } } // Renders inventory item quantity. // // 0x4705A0 static void display_inventory_info(Object* item, int quantity, unsigned char* dest, int pitch, bool a5) { int oldFont = text_curr(); text_font(101); char formattedText[12]; // NOTE: Original code is slightly different and probably used goto. bool draw = false; if (item_get_type(item) == ITEM_TYPE_AMMO) { int ammoQuantity = item_w_max_ammo(item) * (quantity - 1); if (!a5) { ammoQuantity += item_w_curr_ammo(item); } if (ammoQuantity > 99999) { ammoQuantity = 99999; } sprintf(formattedText, "x%d", ammoQuantity); draw = true; } else { if (quantity > 1) { int v9 = quantity; if (a5) { v9 -= 1; } // NOTE: Checking for quantity twice probably means inlined function // or some macro expansion. if (quantity > 1) { if (v9 > 99999) { v9 = 99999; } sprintf(formattedText, "x%d", v9); draw = true; } } } if (draw) { text_to_buf(dest, formattedText, 80, pitch, colorTable[32767]); } text_font(oldFont); } // 0x470650 void display_body(int fid, int inventoryWindowType) { // 0x5190F4 static unsigned int ticker = 0; // 0x5190F8 static int curr_rot = 0; if (elapsed_time(ticker) < INVENTORY_NORMAL_WINDOW_PC_ROTATION_DELAY) { return; } curr_rot += 1; if (curr_rot == ROTATION_COUNT) { curr_rot = 0; } int rotations[2]; if (fid == -1) { rotations[0] = curr_rot; rotations[1] = ROTATION_SE; } else { rotations[0] = ROTATION_SW; rotations[1] = target_stack[target_curr_stack]->rotation; } int fids[2] = { i_fid, fid, }; for (int index = 0; index < 2; index += 1) { int fid = fids[index]; if (fid == -1) { continue; } CacheEntry* handle; Art* art = art_ptr_lock(fid, &handle); if (art == NULL) { continue; } int frame = 0; if (index == 1) { frame = art_frame_max_frame(art) - 1; } int rotation = rotations[index]; unsigned char* frameData = art_frame_data(art, frame, rotation); int framePitch = art_frame_width(art, frame, rotation); int frameWidth = min(framePitch, INVENTORY_BODY_VIEW_WIDTH); int frameHeight = art_frame_length(art, frame, rotation); if (frameHeight > INVENTORY_BODY_VIEW_HEIGHT) { frameHeight = INVENTORY_BODY_VIEW_HEIGHT; } int win; Rect rect; CacheEntry* backrgroundFrmHandle; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { unsigned char* windowBuffer = win_get_buf(barter_back_win); int windowPitch = win_width(barter_back_win); if (index == 1) { rect.ulx = 560; rect.uly = 25; } else { rect.ulx = 15; rect.uly = 25; } rect.lrx = rect.ulx + INVENTORY_BODY_VIEW_WIDTH - 1; rect.lry = rect.uly + INVENTORY_BODY_VIEW_HEIGHT - 1; int frmId = dialog_target_is_party ? 420 : 111; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, frmId, 0, 0, 0); unsigned char* src = art_ptr_lock_data(backgroundFid, 0, 0, &backrgroundFrmHandle); if (src != NULL) { buf_to_buf(src + rect.uly * (scr_size.lrx - scr_size.ulx + 1) + rect.ulx, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, scr_size.lrx - scr_size.ulx + 1, windowBuffer + windowPitch * rect.uly + rect.ulx, windowPitch); } trans_buf_to_buf(frameData, frameWidth, frameHeight, framePitch, windowBuffer + windowPitch * (rect.uly + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.ulx, windowPitch); win = barter_back_win; } else { unsigned char* windowBuffer = win_get_buf(i_wid); int windowPitch = win_width(i_wid); if (index == 1) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { rect.ulx = 426; rect.uly = 39; } else { rect.ulx = 297; rect.uly = 37; } } else { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT) { rect.ulx = 48; rect.uly = 39; } else { rect.ulx = 176; rect.uly = 37; } } rect.lrx = rect.ulx + INVENTORY_BODY_VIEW_WIDTH - 1; rect.lry = rect.uly + INVENTORY_BODY_VIEW_HEIGHT - 1; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); unsigned char* src = art_ptr_lock_data(backgroundFid, 0, 0, &backrgroundFrmHandle); if (src != NULL) { buf_to_buf(src + 537 * rect.uly + rect.ulx, INVENTORY_BODY_VIEW_WIDTH, INVENTORY_BODY_VIEW_HEIGHT, 537, windowBuffer + windowPitch * rect.uly + rect.ulx, windowPitch); } trans_buf_to_buf(frameData, frameWidth, frameHeight, framePitch, windowBuffer + windowPitch * (rect.uly + (INVENTORY_BODY_VIEW_HEIGHT - frameHeight) / 2) + (INVENTORY_BODY_VIEW_WIDTH - frameWidth) / 2 + rect.ulx, windowPitch); win = i_wid; } win_draw_rect(win, &rect); art_ptr_unlock(backrgroundFrmHandle); art_ptr_unlock(handle); } ticker = get_time(); } // 0x470A2C int inven_init() { // 0x5190FC static int num[INVENTORY_WINDOW_CURSOR_COUNT] = { 286, // pointing hand 250, // action arrow 282, // action pick 283, // action menu 266, // blank }; if (inventry_msg_load() == -1) { return -1; } inven_ui_was_disabled = game_ui_is_disabled(); if (inven_ui_was_disabled) { game_ui_enable(); } gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); int index; for (index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) { InventoryCursorData* cursorData = &(imdata[index]); int fid = art_id(OBJ_TYPE_INTERFACE, num[index], 0, 0, 0); Art* frm = art_ptr_lock(fid, &(cursorData->frmHandle)); if (frm == NULL) { break; } cursorData->frm = frm; cursorData->frmData = art_frame_data(frm, 0, 0); cursorData->width = art_frame_width(frm, 0, 0); cursorData->height = art_frame_length(frm, 0, 0); art_frame_hot(frm, 0, 0, &(cursorData->offsetX), &(cursorData->offsetY)); } if (index != INVENTORY_WINDOW_CURSOR_COUNT) { for (; index >= 0; index--) { art_ptr_unlock(imdata[index].frmHandle); } if (inven_ui_was_disabled) { game_ui_disable(0); } message_exit(&inventry_message_file); return -1; } inven_is_initialized = true; im_value = -1; return 0; } // NOTE: Inlined. // // 0x470B8C void inven_exit() { for (int index = 0; index < INVENTORY_WINDOW_CURSOR_COUNT; index++) { art_ptr_unlock(imdata[index].frmHandle); } if (inven_ui_was_disabled) { game_ui_disable(0); } // NOTE: Uninline. inventry_msg_unload(); inven_is_initialized = 0; } // 0x470BCC void inven_set_mouse(int cursor) { immode = cursor; if (cursor != INVENTORY_WINDOW_CURSOR_ARROW || im_value == -1) { InventoryCursorData* cursorData = &(imdata[cursor]); mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); } else { inven_hover_on(-1, im_value); } } // 0x470C2C void inven_hover_on(int btn, int keyCode) { // 0x519110 static Object* last_target = NULL; if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { int x; int y; mouse_get_position(&x, &y); Object* a2a = NULL; if (inven_from_button(keyCode, &a2a, NULL, NULL) != 0) { gmouse_3d_build_pick_frame(x, y, 3, i_wid_max_x, i_wid_max_y); int v5 = 0; int v6 = 0; gmouse_3d_pick_frame_hot(&v5, &v6); InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_PICK]); mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, v5, v6, 0); if (a2a != last_target) { obj_look_at_func(stack[0], a2a, display_msg); } } else { InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_ARROW]); mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); } last_target = a2a; } im_value = keyCode; } // 0x470D1C void inven_hover_off(int btn, int keyCode) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_ARROW]); mouse_set_shape(cursorData->frmData, cursorData->width, cursorData->height, cursorData->width, cursorData->offsetX, cursorData->offsetY, 0); } im_value = -1; } // 0x470D5C static void inven_update_lighting(Object* a1) { if (obj_dude == inven_dude) { int lightDistance; if (a1 != NULL && a1->lightDistance > 4) { lightDistance = a1->lightDistance; } else { lightDistance = 4; } Rect rect; obj_set_light(inven_dude, lightDistance, 0x10000, &rect); tile_refresh_rect(&rect, map_elevation); } } // 0x470DB8 void inven_pickup(int keyCode, int a2) { Object* a1a; Object** v29 = NULL; int count = inven_from_button(keyCode, &a1a, &v29, NULL); if (count == 0) { return; } int v3 = -1; Object* v39 = NULL; Rect rect; switch (keyCode) { case 1006: rect.ulx = 245; rect.uly = 286; if (inven_dude == obj_dude && intface_is_item_right_hand() != HAND_LEFT) { v39 = a1a; } break; case 1007: rect.ulx = 154; rect.uly = 286; if (inven_dude == obj_dude && intface_is_item_right_hand() == HAND_LEFT) { v39 = a1a; } break; case 1008: rect.ulx = 154; rect.uly = 183; break; default: // NOTE: Original code a little bit different, this code path // is only for key codes below 1006. v3 = keyCode - 1000; rect.ulx = 44; rect.uly = 48 * v3 + 35; break; } if (v3 == -1 || pud->items[a2 + v3].quantity <= 1) { unsigned char* windowBuffer = win_get_buf(i_wid); if (i_rhand != i_lhand || a1a != i_lhand) { int height; int width; if (v3 == -1) { height = INVENTORY_LARGE_SLOT_HEIGHT; width = INVENTORY_LARGE_SLOT_WIDTH; } else { height = INVENTORY_SLOT_HEIGHT; width = INVENTORY_SLOT_WIDTH; } CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { buf_to_buf(backgroundFrmData + 499 * rect.uly + rect.ulx, width, height, 499, windowBuffer + 499 * rect.uly + rect.ulx, 499); art_ptr_unlock(backgroundFrmHandle); } rect.lrx = rect.ulx + width - 1; rect.lry = rect.uly + height - 1; } else { CacheEntry* backgroundFrmHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData != NULL) { buf_to_buf(backgroundFrmData + 499 * 286 + 154, 180, 61, 499, windowBuffer + 499 * 286 + 154, 499); art_ptr_unlock(backgroundFrmHandle); } rect.ulx = 154; rect.uly = 286; rect.lrx = rect.ulx + 180 - 1; rect.lry = rect.uly + 61 - 1; } win_draw_rect(i_wid, &rect); } else { display_inventory(a2, v3, INVENTORY_WINDOW_TYPE_NORMAL); } CacheEntry* itemInventoryFrmHandle; int itemInventoryFid = item_inv_fid(a1a); Art* itemInventoryFrm = art_ptr_lock(itemInventoryFid, &itemInventoryFrmHandle); if (itemInventoryFrm != NULL) { int width = art_frame_width(itemInventoryFrm, 0, 0); int height = art_frame_length(itemInventoryFrm, 0, 0); unsigned char* itemInventoryFrmData = art_frame_data(itemInventoryFrm, 0, 0); mouse_set_shape(itemInventoryFrmData, width, height, width, width / 2, height / 2, 0); gsound_play_sfx_file("ipickup1"); } if (v39 != NULL) { inven_update_lighting(NULL); } do { get_input(); display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); if (itemInventoryFrm != NULL) { art_ptr_unlock(itemInventoryFrmHandle); gsound_play_sfx_file("iputdown"); } if (mouse_click_in(INVENTORY_SCROLLER_ABS_X, INVENTORY_SCROLLER_ABS_Y, INVENTORY_SCROLLER_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_SCROLLER_ABS_Y)) { int x; int y; mouse_get_position(&x, &y); int v18 = (y - 39) / 48 + a2; if (v18 < pud->length) { Object* v19 = pud->items[v18].item; if (v19 != a1a) { // TODO: Needs checking usage of v19 if (item_get_type(v19) == ITEM_TYPE_CONTAINER) { if (drop_into_container(v19, a1a, v3, v29, count) == 0) { v3 = 0; } } else { if (drop_ammo_into_weapon(v19, a1a, v29, count, keyCode) == 0) { v3 = 0; } } } } if (v3 == -1) { // TODO: Holy shit, needs refactoring. *v29 = NULL; if (item_add_force(inven_dude, a1a, 1)) { *v29 = a1a; } else if (v29 == &i_worn) { adjust_ac(stack[0], a1a, NULL); } else if (i_rhand == i_lhand) { i_lhand = NULL; i_rhand = NULL; } } } else if (mouse_click_in(INVENTORY_LEFT_HAND_SLOT_ABS_X, INVENTORY_LEFT_HAND_SLOT_ABS_Y, INVENTORY_LEFT_HAND_SLOT_ABS_MAX_X, INVENTORY_LEFT_HAND_SLOT_ABS_MAX_Y)) { if (i_lhand != NULL && item_get_type(i_lhand) == ITEM_TYPE_CONTAINER && i_lhand != a1a) { drop_into_container(i_lhand, a1a, v3, v29, count); } else if (i_lhand == NULL || drop_ammo_into_weapon(i_lhand, a1a, v29, count, keyCode)) { switch_hand(a1a, &i_lhand, v29, keyCode); } } else if (mouse_click_in(INVENTORY_RIGHT_HAND_SLOT_ABS_X, INVENTORY_RIGHT_HAND_SLOT_ABS_Y, INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_X, INVENTORY_RIGHT_HAND_SLOT_ABS_MAX_Y)) { if (i_rhand != NULL && item_get_type(i_rhand) == ITEM_TYPE_CONTAINER && i_rhand != a1a) { drop_into_container(i_rhand, a1a, v3, v29, count); } else if (i_rhand == NULL || drop_ammo_into_weapon(i_rhand, a1a, v29, count, keyCode)) { switch_hand(a1a, &i_rhand, v29, v3); } } else if (mouse_click_in(INVENTORY_ARMOR_SLOT_ABS_X, INVENTORY_ARMOR_SLOT_ABS_Y, INVENTORY_ARMOR_SLOT_ABS_MAX_X, INVENTORY_ARMOR_SLOT_ABS_MAX_Y)) { if (item_get_type(a1a) == ITEM_TYPE_ARMOR) { Object* v21 = i_worn; int v22 = 0; if (v3 != -1) { item_remove_mult(inven_dude, a1a, 1); } if (i_worn != NULL) { if (v29 != NULL) { *v29 = i_worn; } else { i_worn = NULL; v22 = item_add_force(inven_dude, v21, 1); } } else { if (v29 != NULL) { *v29 = i_worn; } } if (v22 != 0) { i_worn = v21; if (v3 != -1) { item_add_force(inven_dude, a1a, 1); } } else { adjust_ac(stack[0], v21, a1a); i_worn = a1a; } } } else if (mouse_click_in(INVENTORY_PC_BODY_VIEW_ABS_X, INVENTORY_PC_BODY_VIEW_ABS_Y, INVENTORY_PC_BODY_VIEW_ABS_MAX_X, INVENTORY_PC_BODY_VIEW_ABS_MAX_Y)) { if (curr_stack != 0) { // TODO: Check this curr_stack - 1, not sure. drop_into_container(stack[curr_stack - 1], a1a, v3, v29, count); } } adjust_fid(); display_stats(); display_inventory(a2, -1, INVENTORY_WINDOW_TYPE_NORMAL); inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); if (inven_dude == obj_dude) { Object* item; if (intface_is_item_right_hand() == HAND_LEFT) { item = inven_left_hand(inven_dude); } else { item = inven_right_hand(inven_dude); } if (item != NULL) { inven_update_lighting(item); } } } // 0x4714E0 void switch_hand(Object* a1, Object** a2, Object** a3, int a4) { if (*a2 != NULL) { if (item_get_type(*a2) == ITEM_TYPE_WEAPON && item_get_type(a1) == ITEM_TYPE_AMMO) { return; } if (a3 != NULL && (a3 != &i_worn || item_get_type(*a2) == ITEM_TYPE_ARMOR)) { if (a3 == &i_worn) { adjust_ac(stack[0], i_worn, *a2); } *a3 = *a2; } else { if (a4 != -1) { item_remove_mult(inven_dude, a1, 1); } Object* itemToAdd = *a2; *a2 = NULL; if (item_add_force(inven_dude, itemToAdd, 1) != 0) { item_add_force(inven_dude, a1, 1); return; } a4 = -1; if (a3 != NULL) { if (a3 == &i_worn) { adjust_ac(stack[0], i_worn, NULL); } *a3 = NULL; } } } else { if (a3 != NULL) { if (a3 == &i_worn) { adjust_ac(stack[0], i_worn, NULL); } *a3 = NULL; } } *a2 = a1; if (a4 != -1) { item_remove_mult(inven_dude, a1, 1); } } // This function removes armor bonuses and effects granted by [oldArmor] and // adds appropriate bonuses and effects granted by [newArmor]. Both [oldArmor] // and [newArmor] can be NULL. // // 0x4715F8 void adjust_ac(Object* critter, Object* oldArmor, Object* newArmor) { int armorClassBonus = stat_get_bonus(critter, STAT_ARMOR_CLASS); int oldArmorClass = item_ar_ac(oldArmor); int newArmorClass = item_ar_ac(newArmor); stat_set_bonus(critter, STAT_ARMOR_CLASS, armorClassBonus - oldArmorClass + newArmorClass); int damageResistanceStat = STAT_DAMAGE_RESISTANCE; int damageThresholdStat = STAT_DAMAGE_THRESHOLD; for (int damageType = 0; damageType < DAMAGE_TYPE_COUNT; damageType += 1) { int damageResistanceBonus = stat_get_bonus(critter, damageResistanceStat); int oldArmorDamageResistance = item_ar_dr(oldArmor, damageType); int newArmorDamageResistance = item_ar_dr(newArmor, damageType); stat_set_bonus(critter, damageResistanceStat, damageResistanceBonus - oldArmorDamageResistance + newArmorDamageResistance); int damageThresholdBonus = stat_get_bonus(critter, damageThresholdStat); int oldArmorDamageThreshold = item_ar_dt(oldArmor, damageType); int newArmorDamageThreshold = item_ar_dt(newArmor, damageType); stat_set_bonus(critter, damageThresholdStat, damageThresholdBonus - oldArmorDamageThreshold + newArmorDamageThreshold); damageResistanceStat += 1; damageThresholdStat += 1; } if (isPartyMember(critter)) { if (oldArmor != NULL) { int perk = item_ar_perk(oldArmor); perk_remove_effect(critter, perk); } if (newArmor != NULL) { int perk = item_ar_perk(newArmor); perk_add_effect(critter, perk); } } } // 0x4716E8 void adjust_fid() { int fid; if (FID_TYPE(inven_dude->fid) == OBJ_TYPE_CRITTER) { Proto* proto; int v0 = art_vault_guy_num; if (proto_ptr(inven_pid, &proto) == -1) { v0 = proto->fid & 0xFFF; } if (i_worn != NULL) { proto_ptr(i_worn->pid, &proto); if (critterGetStat(inven_dude, STAT_GENDER) == GENDER_FEMALE) { v0 = proto->item.data.armor.femaleFid; } else { v0 = proto->item.data.armor.maleFid; } if (v0 == -1) { v0 = art_vault_guy_num; } } int animationCode = 0; if (intface_is_item_right_hand()) { if (i_rhand != NULL) { proto_ptr(i_rhand->pid, &proto); if (proto->item.type == ITEM_TYPE_WEAPON) { animationCode = proto->item.data.weapon.animationCode; } } } else { if (i_lhand != NULL) { proto_ptr(i_lhand->pid, &proto); if (proto->item.type == ITEM_TYPE_WEAPON) { animationCode = proto->item.data.weapon.animationCode; } } } fid = art_id(OBJ_TYPE_CRITTER, v0, 0, animationCode, 0); } else { fid = inven_dude->fid; } i_fid = fid; } // 0x4717E4 void use_inventory_on(Object* a1) { if (inven_init() == -1) { return; } bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_USE_ITEM_ON); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); for (;;) { if (game_user_wants_to_quit != 0) { break; } display_body(-1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); int keyCode = get_input(); switch (keyCode) { case KEY_HOME: stack_offset[curr_stack] = 0; display_inventory(0, -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); break; case KEY_ARROW_UP: if (stack_offset[curr_stack] > 0) { stack_offset[curr_stack] -= 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } break; case KEY_PAGE_UP: stack_offset[curr_stack] -= inven_cur_disp; if (stack_offset[curr_stack] < 0) { stack_offset[curr_stack] = 0; display_inventory(stack_offset[curr_stack], -1, 1); } break; case KEY_END: stack_offset[curr_stack] = pud->length - inven_cur_disp; if (stack_offset[curr_stack] < 0) { stack_offset[curr_stack] = 0; } display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); break; case KEY_ARROW_DOWN: if (stack_offset[curr_stack] + inven_cur_disp < pud->length) { stack_offset[curr_stack] += 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } break; case KEY_PAGE_DOWN: stack_offset[curr_stack] += inven_cur_disp; if (stack_offset[curr_stack] + inven_cur_disp >= pud->length) { stack_offset[curr_stack] = pud->length - inven_cur_disp; if (stack_offset[curr_stack] < 0) { stack_offset[curr_stack] = 0; } } display_inventory(stack_offset[curr_stack], -1, 1); break; case 2500: container_exit(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); break; default: if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (immode == INVENTORY_WINDOW_CURSOR_HAND) { inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW); } else { inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); } } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode < 1000 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_USE_ITEM_ON); } else { int inventoryItemIndex = pud->length - (stack_offset[curr_stack] + keyCode - 1000 + 1); if (inventoryItemIndex < pud->length) { InventoryItem* inventoryItem = &(pud->items[inventoryItemIndex]); if (isInCombat()) { if (obj_dude->data.critter.combat.ap >= 2) { if (action_use_an_item_on_object(obj_dude, a1, inventoryItem->item) != -1) { int actionPoints = obj_dude->data.critter.combat.ap; if (actionPoints < 2) { obj_dude->data.critter.combat.ap = 0; } else { obj_dude->data.critter.combat.ap = actionPoints - 2; } intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } } } else { action_use_an_item_on_object(obj_dude, a1, inventoryItem->item); } keyCode = KEY_ESCAPE; } else { keyCode = -1; } } } } } if (keyCode == KEY_ESCAPE) { break; } } exit_inventory(isoWasEnabled); // NOTE: Uninline. inven_exit(); } // 0x471B70 Object* inven_right_hand(Object* critter) { int i; Inventory* inventory; Object* item; if (i_rhand != NULL && critter == inven_dude) { return i_rhand; } inventory = &(critter->data.inventory); for (i = 0; i < inventory->length; i++) { item = inventory->items[i].item; if (item->flags & OBJECT_IN_RIGHT_HAND) { return item; } } return NULL; } // 0x471BBC Object* inven_left_hand(Object* critter) { int i; Inventory* inventory; Object* item; if (i_lhand != NULL && critter == inven_dude) { return i_lhand; } inventory = &(critter->data.inventory); for (i = 0; i < inventory->length; i++) { item = inventory->items[i].item; if (item->flags & OBJECT_IN_LEFT_HAND) { return item; } } return NULL; } // 0x471C08 Object* inven_worn(Object* critter) { int i; Inventory* inventory; Object* item; if (i_worn != NULL && critter == inven_dude) { return i_worn; } inventory = &(critter->data.inventory); for (i = 0; i < inventory->length; i++) { item = inventory->items[i].item; if (item->flags & OBJECT_WORN) { return item; } } return NULL; } // 0x471CA0 Object* inven_pid_is_carried(Object* obj, int pid) { Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->item->pid == pid) { return inventoryItem->item; } Object* found = inven_pid_is_carried(inventoryItem->item, pid); if (found != NULL) { return found; } } return NULL; } // 0x471CDC int inven_pid_quantity_carried(Object* object, int pid) { int quantity = 0; Inventory* inventory = &(object->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->item->pid == pid) { quantity += inventoryItem->quantity; } quantity += inven_pid_quantity_carried(inventoryItem->item, pid); } return quantity; } // Renders character's summary of SPECIAL stats, equipped armor bonuses, // and weapon's damage/range. // // 0x471D5C void display_stats() { // 0x46E6D0 static const int v56[7] = { STAT_CURRENT_HIT_POINTS, STAT_ARMOR_CLASS, STAT_DAMAGE_THRESHOLD, STAT_DAMAGE_THRESHOLD_LASER, STAT_DAMAGE_THRESHOLD_FIRE, STAT_DAMAGE_THRESHOLD_PLASMA, STAT_DAMAGE_THRESHOLD_EXPLOSION, }; // 0x46E6EC static const int v57[7] = { STAT_MAXIMUM_HIT_POINTS, -1, STAT_DAMAGE_RESISTANCE, STAT_DAMAGE_RESISTANCE_LASER, STAT_DAMAGE_RESISTANCE_FIRE, STAT_DAMAGE_RESISTANCE_PLASMA, STAT_DAMAGE_RESISTANCE_EXPLOSION, }; char formattedText[80]; int oldFont = text_curr(); text_font(101); unsigned char* windowBuffer = win_get_buf(i_wid); int fid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); CacheEntry* backgroundHandle; unsigned char* backgroundData = art_ptr_lock_data(fid, 0, 0, &backgroundHandle); if (backgroundData != NULL) { buf_to_buf(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499); } art_ptr_unlock(backgroundHandle); // Render character name. const char* critterName = critter_name(stack[0]); text_to_buf(windowBuffer + 499 * 44 + 297, critterName, 80, 499, colorTable[992]); draw_line(windowBuffer, 499, 297, 3 * text_height() / 2 + 44, 440, 3 * text_height() / 2 + 44, colorTable[992]); MessageListItem messageListItem; int offset = 499 * 2 * text_height() + 499 * 44 + 297; for (int stat = 0; stat < 7; stat++) { messageListItem.num = stat; if (message_search(&inventry_message_file, &messageListItem)) { text_to_buf(windowBuffer + offset, messageListItem.text, 80, 499, colorTable[992]); } int value = critterGetStat(stack[0], stat); sprintf(formattedText, "%d", value); text_to_buf(windowBuffer + offset + 24, formattedText, 80, 499, colorTable[992]); offset += 499 * text_height(); } offset -= 499 * 7 * text_height(); for (int index = 0; index < 7; index += 1) { messageListItem.num = 7 + index; if (message_search(&inventry_message_file, &messageListItem)) { text_to_buf(windowBuffer + offset + 40, messageListItem.text, 80, 499, colorTable[992]); } if (v57[index] == -1) { int value = critterGetStat(stack[0], v56[index]); sprintf(formattedText, " %d", value); } else { int value1 = critterGetStat(stack[0], v56[index]); int value2 = critterGetStat(stack[0], v57[index]); const char* format = index != 0 ? "%d/%d%%" : "%d/%d"; sprintf(formattedText, format, value1, value2); } text_to_buf(windowBuffer + offset + 104, formattedText, 80, 499, colorTable[992]); offset += 499 * text_height(); } draw_line(windowBuffer, 499, 297, 18 * text_height() / 2 + 48, 440, 18 * text_height() / 2 + 48, colorTable[992]); draw_line(windowBuffer, 499, 297, 26 * text_height() / 2 + 48, 440, 26 * text_height() / 2 + 48, colorTable[992]); Object* itemsInHands[2] = { i_lhand, i_rhand, }; const int hitModes[2] = { HIT_MODE_LEFT_WEAPON_PRIMARY, HIT_MODE_RIGHT_WEAPON_PRIMARY, }; offset += 499 * text_height(); for (int index = 0; index < 2; index += 1) { Object* item = itemsInHands[index]; if (item == NULL) { formattedText[0] = '\0'; // No item messageListItem.num = 14; if (message_search(&inventry_message_file, &messageListItem)) { text_to_buf(windowBuffer + offset, messageListItem.text, 120, 499, colorTable[992]); } offset += 499 * text_height(); // Unarmed dmg: messageListItem.num = 24; if (message_search(&inventry_message_file, &messageListItem)) { // TODO: Figure out why it uses STAT_MELEE_DAMAGE instead of // STAT_UNARMED_DAMAGE. int damage = critterGetStat(stack[0], STAT_MELEE_DAMAGE) + 2; sprintf(formattedText, "%s 1-%d", messageListItem.text, damage); } text_to_buf(windowBuffer + offset, formattedText, 120, 499, colorTable[992]); offset += 3 * 499 * text_height(); continue; } const char* itemName = item_name(item); text_to_buf(windowBuffer + offset, itemName, 140, 499, colorTable[992]); offset += 499 * text_height(); int itemType = item_get_type(item); if (itemType != ITEM_TYPE_WEAPON) { if (itemType == ITEM_TYPE_ARMOR) { // (Not worn) messageListItem.num = 18; if (message_search(&inventry_message_file, &messageListItem)) { text_to_buf(windowBuffer + offset, messageListItem.text, 120, 499, colorTable[992]); } } offset += 3 * 499 * text_height(); continue; } int range = item_w_range(stack[0], hitModes[index]); int damageMin; int damageMax; item_w_damage_min_max(item, &damageMin, &damageMax); int attackType = item_w_subtype(item, hitModes[index]); formattedText[0] = '\0'; int meleeDamage; if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { meleeDamage = critterGetStat(stack[0], STAT_MELEE_DAMAGE); } else { meleeDamage = 0; } messageListItem.num = 15; // Dmg: if (message_search(&inventry_message_file, &messageListItem)) { if (attackType != 4 && range <= 1) { sprintf(formattedText, "%s %d-%d", messageListItem.text, damageMin, damageMax + meleeDamage); } else { MessageListItem rangeMessageListItem; rangeMessageListItem.num = 16; // Rng: if (message_search(&inventry_message_file, &rangeMessageListItem)) { sprintf(formattedText, "%s %d-%d %s %d", messageListItem.text, damageMin, damageMax + meleeDamage, rangeMessageListItem.text, range); } } text_to_buf(windowBuffer + offset, formattedText, 140, 499, colorTable[992]); } offset += 499 * text_height(); if (item_w_max_ammo(item) > 0) { int ammoTypePid = item_w_ammo_pid(item); formattedText[0] = '\0'; messageListItem.num = 17; // Ammo: if (message_search(&inventry_message_file, &messageListItem)) { if (ammoTypePid != -1) { if (item_w_curr_ammo(item) != 0) { const char* ammoName = proto_name(ammoTypePid); int capacity = item_w_max_ammo(item); int quantity = item_w_curr_ammo(item); sprintf(formattedText, "%s %d/%d %s", messageListItem.text, quantity, capacity, ammoName); } else { int capacity = item_w_max_ammo(item); int quantity = item_w_curr_ammo(item); sprintf(formattedText, "%s %d/%d", messageListItem.text, quantity, capacity); } } } else { int capacity = item_w_max_ammo(item); int quantity = item_w_curr_ammo(item); sprintf(formattedText, "%s %d/%d", messageListItem.text, quantity, capacity); } text_to_buf(windowBuffer + offset, formattedText, 140, 499, colorTable[992]); } offset += 2 * 499 * text_height(); } // Total wt: messageListItem.num = 20; if (message_search(&inventry_message_file, &messageListItem)) { if (PID_TYPE(stack[0]->pid) == OBJ_TYPE_CRITTER) { int carryWeight = critterGetStat(stack[0], STAT_CARRY_WEIGHT); int inventoryWeight = item_total_weight(stack[0]); sprintf(formattedText, "%s %d/%d", messageListItem.text, inventoryWeight, carryWeight); int color = colorTable[992]; if (critterIsOverloaded(stack[0])) { color = colorTable[31744]; } text_to_buf(windowBuffer + offset + 15, formattedText, 120, 499, color); } else { int inventoryWeight = item_total_weight(stack[0]); sprintf(formattedText, "%s %d", messageListItem.text, inventoryWeight); text_to_buf(windowBuffer + offset + 30, formattedText, 80, 499, colorTable[992]); } } text_font(oldFont); } // Finds next item of given [itemType] (can be -1 which means any type of // item). // // The [index] is used to control where to continue the search from, -1 - from // the beginning. // // 0x472698 Object* inven_find_type(Object* obj, int itemType, int* indexPtr) { int dummy = -1; if (indexPtr == NULL) { indexPtr = &dummy; } *indexPtr += 1; Inventory* inventory = &(obj->data.inventory); // TODO: Refactor with for loop. if (*indexPtr >= inventory->length) { return NULL; } while (itemType != -1 && item_get_type(inventory->items[*indexPtr].item) != itemType) { *indexPtr += 1; if (*indexPtr >= inventory->length) { return NULL; } } return inventory->items[*indexPtr].item; } // 0x4726EC Object* inven_find_id(Object* obj, int id) { if (obj->id == id) { return obj; } Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item->id == id) { return item; } if (item_get_type(item) == ITEM_TYPE_CONTAINER) { item = inven_find_id(item, id); if (item != NULL) { return item; } } } return NULL; } // 0x472740 Object* inven_index_ptr(Object* obj, int a2) { Inventory* inventory; inventory = &(obj->data.inventory); if (a2 < 0 || a2 >= inventory->length) { return NULL; } return inventory->items[a2].item; } // inven_wield // 0x472758 int inven_wield(Object* a1, Object* a2, int a3) { return invenWieldFunc(a1, a2, a3, true); } // 0x472768 int invenWieldFunc(Object* critter, Object* item, int a3, bool a4) { if (a4) { if (!map_bk_processes_are_disabled()) { register_begin(ANIMATION_REQUEST_RESERVED); } } int itemType = item_get_type(item); if (itemType == ITEM_TYPE_ARMOR) { Object* armor = inven_worn(critter); if (armor != NULL) { armor->flags &= ~OBJECT_WORN; } item->flags |= OBJECT_WORN; int baseFrmId; if (critterGetStat(critter, STAT_GENDER) == GENDER_FEMALE) { baseFrmId = item_ar_female_fid(item); } else { baseFrmId = item_ar_male_fid(item); } if (baseFrmId == -1) { baseFrmId = 1; } if (critter == obj_dude) { if (!map_bk_processes_are_disabled()) { int fid = art_id(OBJ_TYPE_CRITTER, baseFrmId, 0, (critter->fid & 0xF000) >> 12, critter->rotation + 1); register_object_change_fid(critter, fid, 0); } } else { adjust_ac(critter, armor, item); } } else { int hand; if (critter == obj_dude) { hand = intface_is_item_right_hand(); } else { hand = HAND_RIGHT; } int weaponAnimationCode = item_w_anim_code(item); int hitModeAnimationCode = item_w_anim_weap(item, HIT_MODE_RIGHT_WEAPON_PRIMARY); int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, hitModeAnimationCode, weaponAnimationCode, critter->rotation + 1); if (!art_exists(fid)) { debug_printf("\ninven_wield failed! ERROR ERROR ERROR!"); return -1; } Object* v17; if (a3) { v17 = inven_right_hand(critter); item->flags |= OBJECT_IN_RIGHT_HAND; } else { v17 = inven_left_hand(critter); item->flags |= OBJECT_IN_LEFT_HAND; } Rect rect; if (v17 != NULL) { v17->flags &= ~OBJECT_IN_ANY_HAND; if (v17->pid == PROTO_ID_LIT_FLARE) { int lightIntensity; int lightDistance; if (critter == obj_dude) { lightIntensity = LIGHT_LEVEL_MAX; lightDistance = 4; } else { Proto* proto; if (proto_ptr(critter->pid, &proto) == -1) { return -1; } lightDistance = proto->lightDistance; lightIntensity = proto->lightIntensity; } obj_set_light(critter, lightDistance, lightIntensity, &rect); } } if (item->pid == PROTO_ID_LIT_FLARE) { int lightDistance = item->lightDistance; if (lightDistance < critter->lightDistance) { lightDistance = critter->lightDistance; } int lightIntensity = item->lightIntensity; if (lightIntensity < critter->lightIntensity) { lightIntensity = critter->lightIntensity; } obj_set_light(critter, lightDistance, lightIntensity, &rect); tile_refresh_rect(&rect, map_elevation); } if (item_get_type(item) == ITEM_TYPE_WEAPON) { weaponAnimationCode = item_w_anim_code(item); } else { weaponAnimationCode = 0; } if (hand == a3) { if ((critter->fid & 0xF000) >> 12 != 0) { if (a4) { if (!map_bk_processes_are_disabled()) { const char* soundEffectName = gsnd_build_character_sfx_name(critter, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(critter, soundEffectName, 0); register_object_animate(critter, ANIM_PUT_AWAY, 0); } } } if (a4 && !map_bk_processes_are_disabled()) { if (weaponAnimationCode != 0) { register_object_take_out(critter, weaponAnimationCode, -1); } else { int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, 0, critter->rotation + 1); register_object_change_fid(critter, fid, -1); } } else { int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, 0, weaponAnimationCode, critter->rotation + 1); dude_stand(critter, critter->rotation, fid); } } } if (a4) { if (!map_bk_processes_are_disabled()) { return register_end(); } } return 0; } // inven_unwield // 0x472A54 int inven_unwield(Object* critter_obj, int a2) { return invenUnwieldFunc(critter_obj, a2, 1); } // 0x472A64 int invenUnwieldFunc(Object* obj, int a2, int a3) { int v6; Object* item_obj; int fid; if (obj == obj_dude) { v6 = intface_is_item_right_hand(); } else { v6 = 1; } if (a2) { item_obj = inven_right_hand(obj); } else { item_obj = inven_left_hand(obj); } if (item_obj) { item_obj->flags &= ~OBJECT_IN_ANY_HAND; } if (v6 == a2 && ((obj->fid & 0xF000) >> 12) != 0) { if (a3 && !map_bk_processes_are_disabled()) { register_begin(ANIMATION_REQUEST_RESERVED); const char* sfx = gsnd_build_character_sfx_name(obj, ANIM_PUT_AWAY, CHARACTER_SOUND_EFFECT_UNUSED); register_object_play_sfx(obj, sfx, 0); register_object_animate(obj, ANIM_PUT_AWAY, 0); fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, 0, 0, obj->rotation + 1); register_object_change_fid(obj, fid, -1); return register_end(); } fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, 0, 0, obj->rotation + 1); dude_stand(obj, obj->rotation, fid); } return 0; } // 0x472B54 int inven_from_button(int keyCode, Object** a2, Object*** a3, Object** a4) { Object** v6; Object* v7; Object* v8; int quantity = 0; switch (keyCode) { case 1006: v6 = &i_rhand; v7 = stack[0]; v8 = i_rhand; break; case 1007: v6 = &i_lhand; v7 = stack[0]; v8 = i_lhand; break; case 1008: v6 = &i_worn; v7 = stack[0]; v8 = i_worn; break; default: v6 = NULL; v7 = NULL; v8 = NULL; InventoryItem* inventoryItem; if (keyCode < 2000) { int index = stack_offset[curr_stack] + keyCode - 1000; if (index >= pud->length) { break; } inventoryItem = &(pud->items[pud->length - (index + 1)]); v8 = inventoryItem->item; v7 = stack[curr_stack]; } else if (keyCode < 2300) { int index = target_stack_offset[target_curr_stack] + keyCode - 2000; if (index >= target_pud->length) { break; } inventoryItem = &(target_pud->items[target_pud->length - (index + 1)]); v8 = inventoryItem->item; v7 = target_stack[target_curr_stack]; } else if (keyCode < 2400) { int index = ptable_offset + keyCode - 2300; if (index >= ptable_pud->length) { break; } inventoryItem = &(ptable_pud->items[ptable_pud->length - (index + 1)]); v8 = inventoryItem->item; v7 = ptable; } else { int index = btable_offset + keyCode - 2400; if (index >= btable_pud->length) { break; } inventoryItem = &(btable_pud->items[btable_pud->length - (index + 1)]); v8 = inventoryItem->item; v7 = btable; } quantity = inventoryItem->quantity; } if (a3 != NULL) { *a3 = v6; } if (a2 != NULL) { *a2 = v8; } if (a4 != NULL) { *a4 = v7; } if (quantity == 0 && v8 != NULL) { quantity = 1; } return quantity; } // Displays item description. // // The [string] is mutated in the process replacing spaces back and forth // for word wrapping purposes. // // inven_display_msg // 0x472D24 void inven_display_msg(char* string) { int oldFont = text_curr(); text_font(101); unsigned char* windowBuffer = win_get_buf(i_wid); windowBuffer += 499 * 44 + 297; char* c = string; while (c != NULL && *c != '\0') { inven_display_msg_line += 1; if (inven_display_msg_line > 17) { debug_printf("\nError: inven_display_msg: out of bounds!"); return; } char* space = NULL; if (text_width(c) > 152) { // Look for next space. space = c + 1; while (*space != '\0' && *space != ' ') { space += 1; } if (*space == '\0') { // This was the last line containing very long word. Text // drawing routine will silently truncate it after reaching // desired length. text_to_buf(windowBuffer + 499 * inven_display_msg_line * text_height(), c, 152, 499, colorTable[992]); return; } char* nextSpace = space + 1; while (true) { while (*nextSpace != '\0' && *nextSpace != ' ') { nextSpace += 1; } if (*nextSpace == '\0') { break; } // Break string and measure it. *nextSpace = '\0'; if (text_width(c) >= 152) { // Next space is too far to fit in one line. Restore next // space's character and stop. *nextSpace = ' '; break; } space = nextSpace; // Restore next space's character and continue looping from the // next character. *nextSpace = ' '; nextSpace += 1; } if (*space == ' ') { *space = '\0'; } } if (text_width(c) > 152) { debug_printf("\nError: inven_display_msg: word too long!"); return; } text_to_buf(windowBuffer + 499 * inven_display_msg_line * text_height(), c, 152, 499, colorTable[992]); if (space != NULL) { c = space + 1; if (*space == '\0') { *space = ' '; } } else { c = NULL; } } text_font(oldFont); } // Examines inventory item. // // 0x472EB8 void inven_obj_examine_func(Object* critter, Object* item) { int oldFont = text_curr(); text_font(101); unsigned char* windowBuffer = win_get_buf(i_wid); // Clear item description area. int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 48, 0, 0, 0); CacheEntry* handle; unsigned char* backgroundData = art_ptr_lock_data(backgroundFid, 0, 0, &handle); if (backgroundData != NULL) { buf_to_buf(backgroundData + 499 * 44 + 297, 152, 188, 499, windowBuffer + 499 * 44 + 297, 499); } art_ptr_unlock(handle); // Reset item description lines counter. inven_display_msg_line = 0; // Render item's name. char* itemName = object_name(item); inven_display_msg(itemName); // Increment line counter to accomodate separator below. inven_display_msg_line += 1; int lineHeight = text_height(); // Draw separator. draw_line(windowBuffer, 499, 297, 3 * lineHeight / 2 + 49, 440, 3 * lineHeight / 2 + 49, colorTable[992]); // Examine item. obj_examine_func(critter, item, inven_display_msg); // Add weight if neccessary. int weight = item_weight(item); if (weight != 0) { MessageListItem messageListItem; messageListItem.num = 540; if (weight == 1) { messageListItem.num = 541; } if (!message_search(&proto_main_msg_file, &messageListItem)) { debug_printf("\nError: Couldn't find message!"); } char formattedText[40]; sprintf(formattedText, messageListItem.text, weight); inven_display_msg(formattedText); } text_font(oldFont); } // 0x47304C void inven_action_cursor(int keyCode, int inventoryWindowType) { // 0x519114 static int act_use[4] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_USE, GAME_MOUSE_ACTION_MENU_ITEM_DROP, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519124 static int act_no_use[3] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_DROP, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519130 static int act_just_use[3] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_USE, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x51913C static int act_nothing[2] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519144 static int act_weap[4] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD, GAME_MOUSE_ACTION_MENU_ITEM_DROP, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; // 0x519154 static int act_weap2[3] = { GAME_MOUSE_ACTION_MENU_ITEM_LOOK, GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD, GAME_MOUSE_ACTION_MENU_ITEM_CANCEL, }; Object* item; Object** v43; Object* v41; int v56 = inven_from_button(keyCode, &item, &v43, &v41); if (v56 == 0) { return; } int itemType = item_get_type(item); int mouseState; do { get_input(); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); } mouseState = mouse_get_buttons(); if ((mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { obj_look_at_func(stack[0], item, display_msg); } else { inven_obj_examine_func(stack[0], item); } win_draw(i_wid); return; } } while ((mouseState & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT); inven_set_mouse(INVENTORY_WINDOW_CURSOR_BLANK); unsigned char* windowBuffer = win_get_buf(i_wid); int x; int y; mouse_get_position(&x, &y); int actionMenuItemsLength; const int* actionMenuItems; if (itemType == ITEM_TYPE_WEAPON && item_w_can_unload(item)) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL && obj_top_environment(item) != obj_dude) { actionMenuItemsLength = 3; actionMenuItems = act_weap2; } else { actionMenuItemsLength = 4; actionMenuItems = act_weap; } } else { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { if (obj_top_environment(item) != obj_dude) { if (itemType == ITEM_TYPE_CONTAINER) { actionMenuItemsLength = 3; actionMenuItems = act_just_use; } else { actionMenuItemsLength = 2; actionMenuItems = act_nothing; } } else { if (itemType == ITEM_TYPE_CONTAINER) { actionMenuItemsLength = 4; actionMenuItems = act_use; } else { actionMenuItemsLength = 3; actionMenuItems = act_no_use; } } } else { if (itemType == ITEM_TYPE_CONTAINER && v43 != NULL) { actionMenuItemsLength = 3; actionMenuItems = act_no_use; } else { if (obj_action_can_use(item) || proto_action_can_use_on(item->pid)) { actionMenuItemsLength = 4; actionMenuItems = act_use; } else { actionMenuItemsLength = 3; actionMenuItems = act_no_use; } } } } InventoryWindowDescription* windowDescription = &(iscr_data[inventoryWindowType]); gmouse_3d_build_menu_frame(x, y, actionMenuItems, actionMenuItemsLength, windowDescription->width + windowDescription->x, windowDescription->height + windowDescription->y); InventoryCursorData* cursorData = &(imdata[INVENTORY_WINDOW_CURSOR_MENU]); int offsetX; int offsetY; art_frame_offset(cursorData->frm, 0, &offsetX, &offsetY); Rect rect; rect.ulx = x - windowDescription->x - cursorData->width / 2 + offsetX; rect.uly = y - windowDescription->y - cursorData->height + 1 + offsetY; rect.lrx = rect.ulx + cursorData->width - 1; rect.lry = rect.uly + cursorData->height - 1; int menuButtonHeight = cursorData->height; if (rect.uly + menuButtonHeight > windowDescription->height) { menuButtonHeight = windowDescription->height - rect.uly; } int btn = win_register_button(i_wid, rect.ulx, rect.uly, cursorData->width, menuButtonHeight, -1, -1, -1, -1, cursorData->frmData, cursorData->frmData, 0, BUTTON_FLAG_TRANSPARENT); win_draw_rect(i_wid, &rect); int menuItemIndex = 0; int previousMouseY = y; while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_UP) == 0) { get_input(); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL) { display_body(-1, INVENTORY_WINDOW_TYPE_NORMAL); } int x; int y; mouse_get_position(&x, &y); if (y - previousMouseY > 10 || previousMouseY - y > 10) { if (y >= previousMouseY || menuItemIndex <= 0) { if (previousMouseY < y && menuItemIndex < actionMenuItemsLength - 1) { menuItemIndex++; } } else { menuItemIndex--; } gmouse_3d_highlight_menu_frame(menuItemIndex); win_draw_rect(i_wid, &rect); previousMouseY = y; } } win_delete_button(btn); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { unsigned char* src = win_get_buf(barter_back_win); int pitch = scr_size.lrx - scr_size.ulx + 1; buf_to_buf(src + pitch * rect.uly + rect.ulx + 80, cursorData->width, menuButtonHeight, pitch, windowBuffer + windowDescription->width * rect.uly + rect.ulx, windowDescription->width); } else { int backgroundFid = art_id(OBJ_TYPE_INTERFACE, windowDescription->field_0, 0, 0, 0); CacheEntry* backgroundFrmHandle; unsigned char* backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); buf_to_buf(backgroundFrmData + windowDescription->width * rect.uly + rect.ulx, cursorData->width, menuButtonHeight, windowDescription->width, windowBuffer + windowDescription->width * rect.uly + rect.ulx, windowDescription->width); art_ptr_unlock(backgroundFrmHandle); } mouse_set_position(x, y); display_inventory(stack_offset[curr_stack], -1, inventoryWindowType); int actionMenuItem = actionMenuItems[menuItemIndex]; switch (actionMenuItem) { case GAME_MOUSE_ACTION_MENU_ITEM_DROP: if (v43 != NULL) { if (v43 == &i_worn) { adjust_ac(stack[0], item, NULL); } item_add_force(v41, item, 1); v56 = 1; *v43 = NULL; } if (item->pid == PROTO_ID_MONEY) { if (v56 > 1) { v56 = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, v56); } else { v56 = 1; } if (v56 > 0) { if (v56 == 1) { item_caps_set_amount(item, 1); obj_drop(v41, item); } else { if (item_remove_mult(v41, item, v56 - 1) == 0) { Object* a2; if (inven_from_button(keyCode, &a2, &v43, &v41) != 0) { item_caps_set_amount(a2, v56); obj_drop(v41, a2); } else { item_add_force(v41, item, v56 - 1); } } } } } else if (item->pid == PROTO_ID_DYNAMITE_II || item->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) { dropped_explosive = 1; obj_drop(v41, item); } else { if (v56 > 1) { v56 = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, item, v56); for (int index = 0; index < v56; index++) { if (inven_from_button(keyCode, &item, &v43, &v41) != 0) { obj_drop(v41, item); } } } else { obj_drop(v41, item); } } break; case GAME_MOUSE_ACTION_MENU_ITEM_LOOK: if (inventoryWindowType != INVENTORY_WINDOW_TYPE_NORMAL) { obj_examine_func(stack[0], item, display_msg); } else { inven_obj_examine_func(stack[0], item); } break; case GAME_MOUSE_ACTION_MENU_ITEM_USE: switch (itemType) { case ITEM_TYPE_CONTAINER: container_enter(keyCode, inventoryWindowType); break; case ITEM_TYPE_DRUG: if (item_d_take_drug(stack[0], item)) { if (v43 != NULL) { *v43 = NULL; } else { item_remove_mult(v41, item, 1); } obj_connect(item, obj_dude->tile, obj_dude->elevation, NULL); obj_destroy(item); } intface_update_hit_points(true); break; case ITEM_TYPE_WEAPON: case ITEM_TYPE_MISC: if (v43 == NULL) { item_remove_mult(v41, item, 1); } int v21; if (obj_action_can_use(item)) { v21 = protinst_use_item(stack[0], item); } else { v21 = protinst_use_item_on(stack[0], stack[0], item); } if (v21 == 1) { if (v43 != NULL) { *v43 = NULL; } obj_connect(item, obj_dude->tile, obj_dude->elevation, NULL); obj_destroy(item); } else { if (v43 == NULL) { item_add_force(v41, item, 1); } } } break; case GAME_MOUSE_ACTION_MENU_ITEM_UNLOAD: if (v43 == NULL) { item_remove_mult(v41, item, 1); } for (;;) { Object* ammo = item_w_unload(item); if (ammo == NULL) { break; } Rect rect; obj_disconnect(ammo, &rect); item_add_force(v41, ammo, 1); } if (v43 == NULL) { item_add_force(v41, item, 1); } break; default: break; } inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_NORMAL && actionMenuItem != GAME_MOUSE_ACTION_MENU_ITEM_LOOK) { display_stats(); } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_LOOT || inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, inventoryWindowType); } display_inventory(stack_offset[curr_stack], -1, inventoryWindowType); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_TRADE) { display_table_inventories(barter_back_win, ptable, btable, -1); } adjust_fid(); } // 0x473904 int loot_container(Object* a1, Object* a2) { // 0x46E708 static const int arrowFrmIds[INVENTORY_ARROW_FRM_COUNT] = { 122, // left arrow up 123, // left arrow down 124, // right arrow up 125, // right arrow down }; CacheEntry* arrowFrmHandles[INVENTORY_ARROW_FRM_COUNT]; MessageListItem messageListItem; if (a1 != inven_dude) { return 0; } if (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER) { if (critter_flag_check(a2->pid, CRITTER_NO_STEAL)) { // You can't find anything to take from that. messageListItem.num = 50; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } return 0; } } if (FID_TYPE(a2->fid) == OBJ_TYPE_ITEM) { if (item_get_type(a2) == ITEM_TYPE_CONTAINER) { if (a2->frame == 0) { CacheEntry* handle; Art* frm = art_ptr_lock(a2->fid, &handle); if (frm != NULL) { int frameCount = art_frame_max_frame(frm); art_ptr_unlock(handle); if (frameCount > 1) { return 0; } } } } } int sid = -1; if (!gIsSteal) { if (obj_sid(a2, &sid) != -1) { scr_set_objs(sid, a1, NULL); exec_script_proc(sid, SCRIPT_PROC_PICKUP); Script* script; if (scr_ptr(sid, &script) != -1) { if (script->scriptOverrides) { return 0; } } } } if (inven_init() == -1) { return 0; } target_pud = &(a2->data.inventory); target_curr_stack = 0; target_stack_offset[0] = 0; target_stack[0] = a2; Object* a1a = NULL; if (obj_new(&a1a, 0, 467) == -1) { return 0; } item_move_all_hidden(a2, a1a); Object* item1 = NULL; Object* item2 = NULL; Object* armor = NULL; if (gIsSteal) { item1 = inven_left_hand(a2); if (item1 != NULL) { item_remove_mult(a2, item1, 1); } item2 = inven_right_hand(a2); if (item2 != NULL) { item_remove_mult(a2, item2, 1); } armor = inven_worn(a2); if (armor != NULL) { item_remove_mult(a2, armor, 1); } } bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_LOOT); Object** critters = NULL; int critterCount = 0; int critterIndex = 0; if (!gIsSteal) { if (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER) { critterCount = obj_create_list(a2->tile, a2->elevation, OBJ_TYPE_CRITTER, &critters); int endIndex = critterCount - 1; for (int index = 0; index < critterCount; index++) { Object* critter = critters[index]; if ((critter->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) == 0) { critters[index] = critters[endIndex]; critters[endIndex] = critter; critterCount--; index--; endIndex--; } else { critterIndex++; } } if (critterCount == 1) { obj_delete_list(critters); critterCount = 0; } if (critterCount > 1) { int fid; unsigned char* buttonUpData; unsigned char* buttonDownData; int btn; for (int index = 0; index < INVENTORY_ARROW_FRM_COUNT; index++) { arrowFrmHandles[index] = INVALID_CACHE_ENTRY; } // Setup left arrow button. fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_UP], 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_LEFT_ARROW_UP])); fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN], 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_LEFT_ARROW_DOWN])); if (buttonUpData != NULL && buttonDownData != NULL) { btn = win_register_button(i_wid, 436, 162, 20, 18, -1, -1, KEY_PAGE_UP, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } // Setup right arrow button. fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP], 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_RIGHT_ARROW_UP])); fid = art_id(OBJ_TYPE_INTERFACE, arrowFrmIds[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN], 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(arrowFrmHandles[INVENTORY_ARROW_FRM_RIGHT_ARROW_DOWN])); if (buttonUpData != NULL && buttonDownData != NULL) { btn = win_register_button(i_wid, 456, 162, 20, 18, -1, -1, KEY_PAGE_DOWN, -1, buttonUpData, buttonDownData, NULL, 0); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } for (int index = 0; index < critterCount; index++) { if (a2 == critters[index]) { critterIndex = index; } } } } } display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); display_body(a2->fid, INVENTORY_WINDOW_TYPE_LOOT); inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); bool isCaughtStealing = false; int stealingXp = 0; int stealingXpBonus = 10; for (;;) { if (game_user_wants_to_quit != 0) { break; } if (isCaughtStealing) { break; } int keyCode = get_input(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { break; } if (keyCode == KEY_UPPERCASE_A) { if (!gIsSteal) { int maxCarryWeight = critterGetStat(a1, STAT_CARRY_WEIGHT); int currentWeight = item_total_weight(a1); int newInventoryWeight = item_total_weight(a2); if (newInventoryWeight <= maxCarryWeight - currentWeight) { item_move_all(a2, a1); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } else { // Sorry, you cannot carry that much. messageListItem.num = 31; if (message_search(&inventry_message_file, &messageListItem)) { dialog_out(messageListItem.text, NULL, 0, 169, 117, colorTable[32328], NULL, colorTable[32328], 0); } } } } else if (keyCode == KEY_ARROW_UP) { if (stack_offset[curr_stack] > 0) { stack_offset[curr_stack] -= 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_PAGE_UP) { if (critterCount != 0) { if (critterIndex > 0) { critterIndex -= 1; } else { critterIndex = critterCount - 1; } a2 = critters[critterIndex]; target_pud = &(a2->data.inventory); target_stack[0] = a2; target_curr_stack = 0; target_stack_offset[0] = 0; display_target_inventory(0, -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); display_body(a2->fid, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_ARROW_DOWN) { if (stack_offset[curr_stack] + inven_cur_disp < pud->length) { stack_offset[curr_stack] += 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_PAGE_DOWN) { if (critterCount != 0) { if (critterIndex < critterCount - 1) { critterIndex += 1; } else { critterIndex = 0; } a2 = critters[critterIndex]; target_pud = &(a2->data.inventory); target_stack[0] = a2; target_curr_stack = 0; target_stack_offset[0] = 0; display_target_inventory(0, -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); display_body(a2->fid, INVENTORY_WINDOW_TYPE_LOOT); } } else if (keyCode == KEY_CTRL_ARROW_UP) { if (target_stack_offset[target_curr_stack] > 0) { target_stack_offset[target_curr_stack] -= 1; display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); win_draw(i_wid); } } else if (keyCode == KEY_CTRL_ARROW_DOWN) { if (target_stack_offset[target_curr_stack] + inven_cur_disp < target_pud->length) { target_stack_offset[target_curr_stack] += 1; display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); win_draw(i_wid); } } else if (keyCode >= 2500 && keyCode <= 2501) { container_exit(keyCode, INVENTORY_WINDOW_TYPE_LOOT); } else { if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (immode == INVENTORY_WINDOW_CURSOR_HAND) { inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW); } else { inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); } } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode <= 1000 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_LOOT); } else { int v40 = keyCode - 1000; if (v40 + stack_offset[curr_stack] < pud->length) { gStealCount += 1; gStealSize += item_size(stack[curr_stack]); InventoryItem* inventoryItem = &(pud->items[pud->length - (v40 + stack_offset[curr_stack] + 1)]); int rc = move_inventory(inventoryItem->item, v40, target_stack[target_curr_stack], true); if (rc == 1) { isCaughtStealing = true; } else if (rc == 2) { stealingXp += stealingXpBonus; stealingXpBonus += 10; } display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } keyCode = -1; } } else if (keyCode >= 2000 && keyCode <= 2000 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_LOOT); } else { int v46 = keyCode - 2000; if (v46 + target_stack_offset[target_curr_stack] < target_pud->length) { gStealCount += 1; gStealSize += item_size(stack[curr_stack]); InventoryItem* inventoryItem = &(target_pud->items[target_pud->length - (v46 + target_stack_offset[target_curr_stack] + 1)]); int rc = move_inventory(inventoryItem->item, v46, target_stack[target_curr_stack], false); if (rc == 1) { isCaughtStealing = true; } else if (rc == 2) { stealingXp += stealingXpBonus; stealingXpBonus += 10; } display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_LOOT); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_LOOT); } } } } } if (keyCode == KEY_ESCAPE) { break; } } if (critterCount != 0) { obj_delete_list(critters); for (int index = 0; index < INVENTORY_ARROW_FRM_COUNT; index++) { art_ptr_unlock(arrowFrmHandles[index]); } } if (gIsSteal) { if (item1 != NULL) { item1->flags |= OBJECT_IN_LEFT_HAND; item_add_force(a2, item1, 1); } if (item2 != NULL) { item2->flags |= OBJECT_IN_RIGHT_HAND; item_add_force(a2, item2, 1); } if (armor != NULL) { armor->flags |= OBJECT_WORN; item_add_force(a2, armor, 1); } } item_move_all(a1a, a2); obj_erase_object(a1a, NULL); if (gIsSteal) { if (!isCaughtStealing) { if (stealingXp > 0) { if (!isPartyMember(a2)) { stealingXp = min(300 - skill_level(a1, SKILL_STEAL), stealingXp); debug_printf("\n[[[%d]]]", 300 - skill_level(a1, SKILL_STEAL)); // You gain %d experience points for successfully using your Steal skill. messageListItem.num = 29; if (message_search(&inventry_message_file, &messageListItem)) { char formattedText[200]; sprintf(formattedText, messageListItem.text, stealingXp); display_print(formattedText); } stat_pc_add_experience(stealingXp); } } } } exit_inventory(isoWasEnabled); // NOTE: Uninline. inven_exit(); if (gIsSteal) { if (isCaughtStealing) { if (gStealCount > 0) { if (obj_sid(a2, &sid) != -1) { scr_set_objs(sid, a1, NULL); exec_script_proc(sid, SCRIPT_PROC_PICKUP); // TODO: Looks like inlining, script is not used. Script* script; scr_ptr(sid, &script); } } } } return 0; } // 0x4746A0 int inven_steal_container(Object* a1, Object* a2) { if (a1 == a2) { return -1; } gIsSteal = PID_TYPE(a1->pid) == OBJ_TYPE_CRITTER && critter_is_active(a2); gStealCount = 0; gStealSize = 0; int rc = loot_container(a1, a2); gIsSteal = 0; gStealCount = 0; gStealSize = 0; return rc; } // 0x474708 int move_inventory(Object* a1, int a2, Object* a3, bool a4) { bool v38 = true; Rect rect; int quantity; if (a4) { rect.ulx = INVENTORY_LOOT_LEFT_SCROLLER_X; rect.uly = INVENTORY_SLOT_HEIGHT * a2 + INVENTORY_LOOT_LEFT_SCROLLER_Y; InventoryItem* inventoryItem = &(pud->items[pud->length - (a2 + stack_offset[curr_stack] + 1)]); quantity = inventoryItem->quantity; if (quantity > 1) { display_inventory(stack_offset[curr_stack], a2, INVENTORY_WINDOW_TYPE_LOOT); v38 = false; } } else { rect.ulx = INVENTORY_LOOT_RIGHT_SCROLLER_X; rect.uly = INVENTORY_SLOT_HEIGHT * a2 + INVENTORY_LOOT_RIGHT_SCROLLER_Y; InventoryItem* inventoryItem = &(target_pud->items[target_pud->length - (a2 + target_stack_offset[target_curr_stack] + 1)]); quantity = inventoryItem->quantity; if (quantity > 1) { display_target_inventory(target_stack_offset[target_curr_stack], a2, target_pud, INVENTORY_WINDOW_TYPE_LOOT); win_draw(i_wid); v38 = false; } } if (v38) { unsigned char* windowBuffer = win_get_buf(i_wid); CacheEntry* handle; int fid = art_id(OBJ_TYPE_INTERFACE, 114, 0, 0, 0); unsigned char* data = art_ptr_lock_data(fid, 0, 0, &handle); if (data != NULL) { buf_to_buf(data + 537 * rect.uly + rect.ulx, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, 537, windowBuffer + 537 * rect.uly + rect.ulx, 537); art_ptr_unlock(handle); } rect.lrx = rect.ulx + INVENTORY_SLOT_WIDTH - 1; rect.lry = rect.uly + INVENTORY_SLOT_HEIGHT - 1; win_draw_rect(i_wid, &rect); } CacheEntry* inventoryFrmHandle; int inventoryFid = item_inv_fid(a1); Art* inventoryFrm = art_ptr_lock(inventoryFid, &inventoryFrmHandle); if (inventoryFrm != NULL) { int width = art_frame_width(inventoryFrm, 0, 0); int height = art_frame_length(inventoryFrm, 0, 0); unsigned char* data = art_frame_data(inventoryFrm, 0, 0); mouse_set_shape(data, width, height, width, width / 2, height / 2, 0); gsound_play_sfx_file("ipickup1"); } do { get_input(); } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); if (inventoryFrm != NULL) { art_ptr_unlock(inventoryFrmHandle); gsound_play_sfx_file("iputdown"); } int rc = 0; MessageListItem messageListItem; if (a4) { if (mouse_click_in(INVENTORY_LOOT_RIGHT_SCROLLER_ABS_X, INVENTORY_LOOT_RIGHT_SCROLLER_ABS_Y, INVENTORY_LOOT_RIGHT_SCROLLER_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_LOOT_RIGHT_SCROLLER_ABS_Y)) { int quantityToMove; if (quantity > 1) { quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity); } else { quantityToMove = 1; } if (quantityToMove != -1) { if (gIsSteal) { if (skill_check_stealing(inven_dude, a3, a1, true) == 0) { rc = 1; } } if (rc != 1) { if (item_move(inven_dude, a3, a1, quantityToMove) != -1) { rc = 2; } else { // There is no space left for that item. messageListItem.num = 26; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } } } else { if (mouse_click_in(INVENTORY_LOOT_LEFT_SCROLLER_ABS_X, INVENTORY_LOOT_LEFT_SCROLLER_ABS_Y, INVENTORY_LOOT_LEFT_SCROLLER_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_LOOT_LEFT_SCROLLER_ABS_Y)) { int quantityToMove; if (quantity > 1) { quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity); } else { quantityToMove = 1; } if (quantityToMove != -1) { if (gIsSteal) { if (skill_check_stealing(inven_dude, a3, a1, false) == 0) { rc = 1; } } if (rc != 1) { if (item_move(a3, inven_dude, a1, quantityToMove) == 0) { if ((a1->flags & OBJECT_IN_RIGHT_HAND) != 0) { a3->fid = art_id(FID_TYPE(a3->fid), a3->fid & 0xFFF, FID_ANIM_TYPE(a3->fid), 0, a3->rotation + 1); } a3->flags &= ~OBJECT_EQUIPPED; rc = 2; } else { // You cannot pick that up. You are at your maximum weight capacity. messageListItem.num = 25; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } } } inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); return rc; } // 0x474B2C static int barter_compute_value(Object* a1, Object* a2) { if (dialog_target_is_party) { return item_total_weight(btable); } int cost = item_total_cost(btable); int caps = item_caps_total(btable); int v14 = cost - caps; double bonus = 0.0; if (a1 == obj_dude) { if (perkHasRank(obj_dude, PERK_MASTER_TRADER)) { bonus = 25.0; } } int partyBarter = partyMemberHighestSkillLevel(SKILL_BARTER); int npcBarter = skill_level(a2, SKILL_BARTER); // TODO: Check in debugger, complex math, probably uses floats, not doubles. double v1 = (barter_mod + 100.0 - bonus) * 0.01; double v2 = (160.0 + npcBarter) / (160.0 + partyBarter) * (v14 * 2.0); if (v1 < 0) { // TODO: Probably 0.01 as float. v1 = 0.0099999998; } int rounded = (int)(v1 * v2 + caps); return rounded; } // 0x474C50 static int barter_attempt_transaction(Object* a1, Object* a2, Object* a3, Object* a4) { MessageListItem messageListItem; int v8 = critterGetStat(a1, STAT_CARRY_WEIGHT) - item_total_weight(a1); if (item_total_weight(a4) > v8) { // Sorry, you cannot carry that much. messageListItem.num = 31; if (message_search(&inventry_message_file, &messageListItem)) { gdialogDisplayMsg(messageListItem.text); } return -1; } if (dialog_target_is_party) { int v10 = critterGetStat(a3, STAT_CARRY_WEIGHT) - item_total_weight(a3); if (item_total_weight(a2) > v10) { // Sorry, that's too much to carry. messageListItem.num = 32; if (message_search(&inventry_message_file, &messageListItem)) { gdialogDisplayMsg(messageListItem.text); } return -1; } } else { bool v11 = false; if (a2->data.inventory.length == 0) { v11 = true; } else { if (item_queued(a2)) { if (a2->pid != PROTO_ID_GEIGER_COUNTER_I || item_m_turn_off(a2) == -1) { v11 = true; } } } if (!v11) { int cost = item_total_cost(a2); if (barter_compute_value(a1, a3) > cost) { v11 = true; } } if (v11) { // No, your offer is not good enough. messageListItem.num = 28; if (message_search(&inventry_message_file, &messageListItem)) { gdialogDisplayMsg(messageListItem.text); } return -1; } } item_move_all(a4, a1); item_move_all(a2, a3); return 0; } // 0x474DAC static void barter_move_inventory(Object* a1, int quantity, int a3, int a4, Object* a5, Object* a6, bool a7) { Rect rect; if (a7) { rect.ulx = 23; rect.uly = 48 * a3 + 34; } else { rect.ulx = 395; rect.uly = 48 * a3 + 31; } if (quantity > 1) { if (a7) { display_inventory(a4, a3, INVENTORY_WINDOW_TYPE_TRADE); } else { display_target_inventory(a4, a3, target_pud, INVENTORY_WINDOW_TYPE_TRADE); } } else { unsigned char* dest = win_get_buf(i_wid); unsigned char* src = win_get_buf(barter_back_win); int pitch = scr_size.lrx - scr_size.ulx + 1; buf_to_buf(src + pitch * rect.uly + rect.ulx + 80, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + 480 * rect.uly + rect.ulx, 480); rect.lrx = rect.ulx + INVENTORY_SLOT_WIDTH - 1; rect.lry = rect.uly + INVENTORY_SLOT_HEIGHT - 1; win_draw_rect(i_wid, &rect); } CacheEntry* inventoryFrmHandle; int inventoryFid = item_inv_fid(a1); Art* inventoryFrm = art_ptr_lock(inventoryFid, &inventoryFrmHandle); if (inventoryFrm != NULL) { int width = art_frame_width(inventoryFrm, 0, 0); int height = art_frame_length(inventoryFrm, 0, 0); unsigned char* data = art_frame_data(inventoryFrm, 0, 0); mouse_set_shape(data, width, height, width, width / 2, height / 2, 0); gsound_play_sfx_file("ipickup1"); } do { get_input(); } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); if (inventoryFrm != NULL) { art_ptr_unlock(inventoryFrmHandle); gsound_play_sfx_file("iputdown"); } MessageListItem messageListItem; if (a7) { if (mouse_click_in(INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_LEFT_SCROLLER_TRACKING_ABS_Y)) { int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1; if (quantityToMove != -1) { if (item_move_force(inven_dude, a6, a1, quantityToMove) == -1) { // There is no space left for that item. messageListItem.num = 26; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } } else { if (mouse_click_in(INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_INNER_RIGHT_SCROLLER_TRACKING_ABS_Y)) { int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1; if (quantityToMove != -1) { if (item_move_force(a5, a6, a1, quantityToMove) == -1) { // You cannot pick that up. You are at your maximum weight capacity. messageListItem.num = 25; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } } inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); } // 0x475070 static void barter_move_from_table_inventory(Object* a1, int quantity, int a3, Object* a4, Object* a5, bool a6) { Rect rect; if (a6) { rect.ulx = 169; rect.uly = 48 * a3 + 24; } else { rect.ulx = 254; rect.uly = 48 * a3 + 24; } if (quantity > 1) { if (a6) { display_table_inventories(barter_back_win, a5, NULL, a3); } else { display_table_inventories(barter_back_win, NULL, a5, a3); } } else { unsigned char* dest = win_get_buf(i_wid); unsigned char* src = win_get_buf(barter_back_win); int pitch = scr_size.lrx - scr_size.ulx + 1; buf_to_buf(src + pitch * rect.uly + rect.ulx + 80, INVENTORY_SLOT_WIDTH, INVENTORY_SLOT_HEIGHT, pitch, dest + 480 * rect.uly + rect.ulx, 480); rect.lrx = rect.ulx + INVENTORY_SLOT_WIDTH - 1; rect.lry = rect.uly + INVENTORY_SLOT_HEIGHT - 1; win_draw_rect(i_wid, &rect); } CacheEntry* inventoryFrmHandle; int inventoryFid = item_inv_fid(a1); Art* inventoryFrm = art_ptr_lock(inventoryFid, &inventoryFrmHandle); if (inventoryFrm != NULL) { int width = art_frame_width(inventoryFrm, 0, 0); int height = art_frame_length(inventoryFrm, 0, 0); unsigned char* data = art_frame_data(inventoryFrm, 0, 0); mouse_set_shape(data, width, height, width, width / 2, height / 2, 0); gsound_play_sfx_file("ipickup1"); } do { get_input(); } while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0); if (inventoryFrm != NULL) { art_ptr_unlock(inventoryFrmHandle); gsound_play_sfx_file("iputdown"); } MessageListItem messageListItem; if (a6) { if (mouse_click_in(INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_INNER_LEFT_SCROLLER_TRACKING_ABS_Y)) { int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1; if (quantityToMove != -1) { if (item_move_force(a5, inven_dude, a1, quantityToMove) == -1) { // There is no space left for that item. messageListItem.num = 26; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } } else { if (mouse_click_in(INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_X, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_Y, INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_MAX_X, INVENTORY_SLOT_HEIGHT * inven_cur_disp + INVENTORY_TRADE_RIGHT_SCROLLER_TRACKING_ABS_Y)) { int quantityToMove = quantity > 1 ? do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a1, quantity) : 1; if (quantityToMove != -1) { if (item_move_force(a5, a4, a1, quantityToMove) == -1) { // You cannot pick that up. You are at your maximum weight capacity. messageListItem.num = 25; if (message_search(&inventry_message_file, &messageListItem)) { display_print(messageListItem.text); } } } } } inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); } // 0x475334 static void display_table_inventories(int win, Object* a2, Object* a3, int a4) { unsigned char* windowBuffer = win_get_buf(i_wid); int oldFont = text_curr(); text_font(101); char formattedText[80]; int v45 = text_height() + 48 * inven_cur_disp; if (a2 != NULL) { unsigned char* src = win_get_buf(win); buf_to_buf(src + (scr_size.lrx - scr_size.ulx + 1) * 20 + 249, 64, v45 + 1, scr_size.lrx - scr_size.ulx + 1, windowBuffer + 480 * 20 + 169, 480); unsigned char* dest = windowBuffer + 480 * 24 + 169; Inventory* inventory = &(a2->data.inventory); for (int index = 0; index < inven_cur_disp && index + ptable_offset < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + ptable_offset + 1)]); int inventoryFid = item_inv_fid(inventoryItem->item); scale_art(inventoryFid, dest, 56, 40, 480); display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4); dest += 480 * 48; } if (dialog_target_is_party) { MessageListItem messageListItem; messageListItem.num = 30; if (message_search(&inventry_message_file, &messageListItem)) { int weight = item_total_weight(a2); sprintf(formattedText, "%s %d", messageListItem.text, weight); } } else { int cost = item_total_cost(a2); sprintf(formattedText, "$%d", cost); } text_to_buf(windowBuffer + 480 * (48 * inven_cur_disp + 24) + 169, formattedText, 80, 480, colorTable[32767]); Rect rect; rect.ulx = 169; rect.uly = 24; rect.lrx = 223; rect.lry = rect.uly + v45; win_draw_rect(i_wid, &rect); } if (a3 != NULL) { unsigned char* src = win_get_buf(win); buf_to_buf(src + (scr_size.lrx - scr_size.ulx + 1) * 20 + 334, 64, v45 + 1, scr_size.lrx - scr_size.ulx + 1, windowBuffer + 480 * 20 + 254, 480); unsigned char* dest = windowBuffer + 480 * 24 + 254; Inventory* inventory = &(a3->data.inventory); for (int index = 0; index < inven_cur_disp && index + btable_offset < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[inventory->length - (index + btable_offset + 1)]); int inventoryFid = item_inv_fid(inventoryItem->item); scale_art(inventoryFid, dest, 56, 40, 480); display_inventory_info(inventoryItem->item, inventoryItem->quantity, dest, 480, index == a4); dest += 480 * 48; } if (dialog_target_is_party) { MessageListItem messageListItem; messageListItem.num = 30; if (message_search(&inventry_message_file, &messageListItem)) { int weight = barter_compute_value(obj_dude, target_stack[0]); sprintf(formattedText, "%s %d", messageListItem.text, weight); } } else { int cost = barter_compute_value(obj_dude, target_stack[0]); sprintf(formattedText, "$%d", cost); } text_to_buf(windowBuffer + 480 * (48 * inven_cur_disp + 24) + 254, formattedText, 80, 480, colorTable[32767]); Rect rect; rect.ulx = 254; rect.uly = 24; rect.lrx = 318; rect.lry = rect.uly + v45; win_draw_rect(i_wid, &rect); } text_font(oldFont); } // 0x4757F0 void barter_inventory(int win, Object* a2, Object* a3, Object* a4, int a5) { barter_mod = a5; if (inven_init() == -1) { return; } Object* armor = inven_worn(a2); if (armor != NULL) { item_remove_mult(a2, armor, 1); } Object* item1 = NULL; Object* item2 = inven_right_hand(a2); if (item2 != NULL) { item_remove_mult(a2, item2, 1); } else { if (!dialog_target_is_party) { item1 = inven_find_type(a2, ITEM_TYPE_WEAPON, NULL); if (item1 != NULL) { item_remove_mult(a2, item1, 1); } } } Object* a1a = NULL; if (obj_new(&a1a, 0, 467) == -1) { return; } pud = &(inven_dude->data.inventory); btable = a4; ptable = a3; ptable_offset = 0; btable_offset = 0; ptable_pud = &(a3->data.inventory); btable_pud = &(a4->data.inventory); barter_back_win = win; target_curr_stack = 0; target_pud = &(a2->data.inventory); target_stack[0] = a2; target_stack_offset[0] = 0; bool isoWasEnabled = setup_inventory(INVENTORY_WINDOW_TYPE_TRADE); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); display_inventory(stack_offset[0], -1, INVENTORY_WINDOW_TYPE_TRADE); display_body(a2->fid, INVENTORY_WINDOW_TYPE_TRADE); win_draw(barter_back_win); display_table_inventories(win, a3, a4, -1); inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); int modifier; int npcReactionValue = reaction_get(a2); int npcReactionType = reaction_to_level(npcReactionValue); switch (npcReactionType) { case NPC_REACTION_BAD: modifier = 25; break; case NPC_REACTION_NEUTRAL: modifier = 0; break; case NPC_REACTION_GOOD: modifier = -15; break; default: assert(false && "Should be unreachable"); } int keyCode = -1; for (;;) { if (keyCode == KEY_ESCAPE || game_user_wants_to_quit != 0) { break; } keyCode = get_input(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { break; } barter_mod = a5 + modifier; if (keyCode == KEY_LOWERCASE_T || modifier <= -30) { item_move_all(a4, a2); item_move_all(a3, obj_dude); barter_end_to_talk_to(); break; } else if (keyCode == KEY_LOWERCASE_M) { if (a3->data.inventory.length != 0 || btable->data.inventory.length != 0) { if (barter_attempt_transaction(inven_dude, a3, a2, a4) == 0) { display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, a3, a4, -1); // Ok, that's a good trade. MessageListItem messageListItem; messageListItem.num = 27; if (!dialog_target_is_party) { if (message_search(&inventry_message_file, &messageListItem)) { gdialogDisplayMsg(messageListItem.text); } } } } } else if (keyCode == KEY_ARROW_UP) { if (stack_offset[curr_stack] > 0) { stack_offset[curr_stack] -= 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } else if (keyCode == KEY_PAGE_UP) { if (ptable_offset > 0) { ptable_offset -= 1; display_table_inventories(win, a3, a4, -1); } } else if (keyCode == KEY_ARROW_DOWN) { if (stack_offset[curr_stack] + inven_cur_disp < pud->length) { stack_offset[curr_stack] += 1; display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); } } else if (keyCode == KEY_PAGE_DOWN) { if (ptable_offset + inven_cur_disp < ptable_pud->length) { ptable_offset += 1; display_table_inventories(win, a3, a4, -1); } } else if (keyCode == KEY_CTRL_PAGE_DOWN) { if (btable_offset + inven_cur_disp < btable_pud->length) { btable_offset++; display_table_inventories(win, a3, a4, -1); } } else if (keyCode == KEY_CTRL_PAGE_UP) { if (btable_offset > 0) { btable_offset -= 1; display_table_inventories(win, a3, a4, -1); } } else if (keyCode == KEY_CTRL_ARROW_UP) { if (target_stack_offset[target_curr_stack] > 0) { target_stack_offset[target_curr_stack] -= 1; display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); win_draw(i_wid); } } else if (keyCode == KEY_CTRL_ARROW_DOWN) { if (target_stack_offset[target_curr_stack] + inven_cur_disp < target_pud->length) { target_stack_offset[target_curr_stack] += 1; display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); win_draw(i_wid); } } else if (keyCode >= 2500 && keyCode <= 2501) { container_exit(keyCode, INVENTORY_WINDOW_TYPE_TRADE); } else { if ((mouse_get_buttons() & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0) { if (immode == INVENTORY_WINDOW_CURSOR_HAND) { inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW); } else { inven_set_mouse(INVENTORY_WINDOW_CURSOR_HAND); } } else if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { if (keyCode >= 1000 && keyCode <= 1000 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, a3, NULL, -1); } else { int v30 = keyCode - 1000; if (v30 + stack_offset[curr_stack] < pud->length) { int v31 = stack_offset[curr_stack]; InventoryItem* inventoryItem = &(pud->items[pud->length - (v30 + v31 + 1)]); barter_move_inventory(inventoryItem->item, inventoryItem->quantity, v30, v31, a2, a3, true); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, a3, NULL, -1); } } keyCode = -1; } else if (keyCode >= 2000 && keyCode <= 2000 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, NULL, a4, -1); } else { int v35 = keyCode - 2000; if (v35 + target_stack_offset[target_curr_stack] < target_pud->length) { int v36 = target_stack_offset[target_curr_stack]; InventoryItem* inventoryItem = &(target_pud->items[target_pud->length - (v35 + v36 + 1)]); barter_move_inventory(inventoryItem->item, inventoryItem->quantity, v35, v36, a2, a4, false); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, NULL, a4, -1); } } keyCode = -1; } else if (keyCode >= 2300 && keyCode <= 2300 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, a3, NULL, -1); } else { int v41 = keyCode - 2300; if (v41 < ptable_pud->length) { InventoryItem* inventoryItem = &(ptable_pud->items[ptable_pud->length - (v41 + ptable_offset + 1)]); barter_move_from_table_inventory(inventoryItem->item, inventoryItem->quantity, v41, a2, a3, true); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, a3, NULL, -1); } } keyCode = -1; } else if (keyCode >= 2400 && keyCode <= 2400 + inven_cur_disp) { if (immode == INVENTORY_WINDOW_CURSOR_ARROW) { inven_action_cursor(keyCode, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, NULL, a4, -1); } else { int v45 = keyCode - 2400; if (v45 < btable_pud->length) { InventoryItem* inventoryItem = &(btable_pud->items[btable_pud->length - (v45 + btable_offset + 1)]); barter_move_from_table_inventory(inventoryItem->item, inventoryItem->quantity, v45, a2, a4, false); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, INVENTORY_WINDOW_TYPE_TRADE); display_inventory(stack_offset[curr_stack], -1, INVENTORY_WINDOW_TYPE_TRADE); display_table_inventories(win, NULL, a4, -1); } } keyCode = -1; } } } } item_move_all(a1a, a2); obj_erase_object(a1a, NULL); if (armor != NULL) { armor->flags |= OBJECT_WORN; item_add_force(a2, armor, 1); } if (item2 != NULL) { item2->flags |= OBJECT_IN_RIGHT_HAND; item_add_force(a2, item2, 1); } if (item1 != NULL) { item_add_force(a2, item1, 1); } exit_inventory(isoWasEnabled); // NOTE: Uninline. inven_exit(); } // 0x47620C void container_enter(int keyCode, int inventoryWindowType) { if (keyCode >= 2000) { int index = target_pud->length - (target_stack_offset[target_curr_stack] + keyCode - 2000 + 1); if (index < target_pud->length && target_curr_stack < 9) { InventoryItem* inventoryItem = &(target_pud->items[index]); Object* item = inventoryItem->item; if (item_get_type(item) == ITEM_TYPE_CONTAINER) { target_curr_stack += 1; target_stack[target_curr_stack] = item; target_stack_offset[target_curr_stack] = 0; target_pud = &(item->data.inventory); display_body(item->fid, inventoryWindowType); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, inventoryWindowType); win_draw(i_wid); } } } else { int index = pud->length - (stack_offset[curr_stack] + keyCode - 1000 + 1); if (index < pud->length && curr_stack < 9) { InventoryItem* inventoryItem = &(pud->items[index]); Object* item = inventoryItem->item; if (item_get_type(item) == ITEM_TYPE_CONTAINER) { curr_stack += 1; stack[curr_stack] = item; stack_offset[curr_stack] = 0; inven_dude = stack[curr_stack]; pud = &(item->data.inventory); adjust_fid(); display_body(-1, inventoryWindowType); display_inventory(stack_offset[curr_stack], -1, inventoryWindowType); } } } } // 0x476394 void container_exit(int keyCode, int inventoryWindowType) { if (keyCode == 2500) { if (curr_stack > 0) { curr_stack -= 1; inven_dude = stack[curr_stack]; pud = &inven_dude->data.inventory; adjust_fid(); display_body(-1, inventoryWindowType); display_inventory(stack_offset[curr_stack], -1, inventoryWindowType); } } else if (keyCode == 2501) { if (target_curr_stack > 0) { target_curr_stack -= 1; Object* v5 = target_stack[target_curr_stack]; target_pud = &(v5->data.inventory); display_body(v5->fid, inventoryWindowType); display_target_inventory(target_stack_offset[target_curr_stack], -1, target_pud, inventoryWindowType); win_draw(i_wid); } } } // 0x476464 int drop_into_container(Object* a1, Object* a2, int a3, Object** a4, int quantity) { int quantityToMove; if (quantity > 1) { quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, a2, quantity); } else { quantityToMove = 1; } if (quantityToMove == -1) { return -1; } if (a3 != -1) { if (item_remove_mult(inven_dude, a2, quantityToMove) == -1) { return -1; } } int rc = item_add_mult(a1, a2, quantityToMove); if (rc != 0) { if (a3 != -1) { item_add_mult(inven_dude, a2, quantityToMove); } } else { if (a4 != NULL) { if (a4 == &i_worn) { adjust_ac(stack[0], i_worn, NULL); } *a4 = NULL; } } return rc; } // 0x47650C int drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** a3, int quantity, int keyCode) { if (item_get_type(weapon) != ITEM_TYPE_WEAPON) { return -1; } if (item_get_type(ammo) != ITEM_TYPE_AMMO) { return -1; } if (!item_w_can_reload(weapon, ammo)) { return -1; } int quantityToMove; if (quantity > 1) { quantityToMove = do_move_timer(INVENTORY_WINDOW_TYPE_MOVE_ITEMS, ammo, quantity); } else { quantityToMove = 1; } if (quantityToMove == -1) { return -1; } Object* v14 = ammo; bool v17 = false; int rc = item_remove_mult(inven_dude, weapon, 1); for (int index = 0; index < quantityToMove; index++) { int v11 = item_w_reload(weapon, v14); if (v11 == 0) { if (a3 != NULL) { *a3 = NULL; } obj_destroy(v14); v17 = true; if (inven_from_button(keyCode, &v14, NULL, NULL) == 0) { break; } } if (v11 != -1) { v17 = true; } if (v11 != 0) { break; } } if (rc != -1) { item_add_force(inven_dude, weapon, 1); } if (!v17) { return -1; } const char* sfx = gsnd_build_weapon_sfx_name(WEAPON_SOUND_EFFECT_READY, weapon, HIT_MODE_RIGHT_WEAPON_PRIMARY, NULL); gsound_play_sfx_file(sfx); return 0; } // 0x47664C void draw_amount(int value, int inventoryWindowType) { // BIGNUM.frm CacheEntry* handle; int fid = art_id(OBJ_TYPE_INTERFACE, 170, 0, 0, 0); unsigned char* data = art_ptr_lock_data(fid, 0, 0, &handle); if (data == NULL) { return; } Rect rect; int windowWidth = win_width(mt_wid); unsigned char* windowBuffer = win_get_buf(mt_wid); if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { rect.ulx = 125; rect.uly = 45; rect.lrx = 195; rect.lry = 69; int ranks[5]; ranks[4] = value % 10; ranks[3] = value / 10 % 10; ranks[2] = value / 100 % 10; ranks[1] = value / 1000 % 10; ranks[0] = value / 10000 % 10; windowBuffer += rect.uly * windowWidth + rect.ulx; for (int index = 0; index < 5; index++) { unsigned char* src = data + 14 * ranks[index]; buf_to_buf(src, 14, 24, 336, windowBuffer, windowWidth); windowBuffer += 14; } } else { rect.ulx = 133; rect.uly = 64; rect.lrx = 189; rect.lry = 88; windowBuffer += windowWidth * rect.uly + rect.ulx; buf_to_buf(data + 14 * (value / 60), 14, 24, 336, windowBuffer, windowWidth); buf_to_buf(data + 14 * (value % 60 / 10), 14, 24, 336, windowBuffer + 14 * 2, windowWidth); buf_to_buf(data + 14 * (value % 10), 14, 24, 336, windowBuffer + 14 * 3, windowWidth); } art_ptr_unlock(handle); win_draw_rect(mt_wid, &rect); } // 0x47688C static int do_move_timer(int inventoryWindowType, Object* item, int max) { setup_move_timer_win(inventoryWindowType, item); int value; int min; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { value = 1; if (max > 99999) { max = 99999; } min = 1; } else { value = 60; min = 10; } draw_amount(value, inventoryWindowType); bool v5 = false; for (;;) { int keyCode = get_input(); if (keyCode == KEY_ESCAPE) { exit_move_timer_win(inventoryWindowType); return -1; } if (keyCode == KEY_RETURN) { if (value >= min && value <= max) { if (inventoryWindowType != INVENTORY_WINDOW_TYPE_SET_TIMER || value % 10 == 0) { gsound_play_sfx_file("ib1p1xx1"); break; } } gsound_play_sfx_file("iisxxxx1"); } else if (keyCode == 5000) { v5 = false; value = max; draw_amount(value, inventoryWindowType); } else if (keyCode == 6000) { v5 = false; if (value < max) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { get_time(); unsigned int delay = 100; while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { if (value < max) { value++; } draw_amount(value, inventoryWindowType); get_input(); if (delay > 1) { delay--; pause_for_tocks(delay); } } } else { if (value < max) { value++; } } } else { value += 10; } draw_amount(value, inventoryWindowType); continue; } } else if (keyCode == 7000) { v5 = false; if (value > min) { if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { get_time(); unsigned int delay = 100; while ((mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT) != 0) { if (value > min) { value--; } draw_amount(value, inventoryWindowType); get_input(); if (delay > 1) { delay--; pause_for_tocks(delay); } } } else { if (value > min) { value--; } } } else { value -= 10; } draw_amount(value, inventoryWindowType); continue; } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { if (keyCode >= KEY_0 && keyCode <= KEY_9) { int number = keyCode - KEY_0; if (!v5) { value = 0; } value = 10 * value % 100000 + number; v5 = true; draw_amount(value, inventoryWindowType); continue; } else if (keyCode == KEY_BACKSPACE) { if (!v5) { value = 0; } value /= 10; v5 = true; draw_amount(value, inventoryWindowType); continue; } } } exit_move_timer_win(inventoryWindowType); return value; } // Creates move items/set timer interface. // // 0x476AB8 static int setup_move_timer_win(int inventoryWindowType, Object* item) { const int oldFont = text_curr(); text_font(103); for (int index = 0; index < 8; index++) { mt_key[index] = NULL; } InventoryWindowDescription* windowDescription = &(iscr_data[inventoryWindowType]); int quantityWindowX = windowDescription->x; int quantityWindowY = windowDescription->y; mt_wid = win_add(quantityWindowX, quantityWindowY, windowDescription->width, windowDescription->height, 257, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); unsigned char* windowBuffer = win_get_buf(mt_wid); CacheEntry* backgroundHandle; int backgroundFid = art_id(OBJ_TYPE_INTERFACE, windowDescription->field_0, 0, 0, 0); unsigned char* backgroundData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundHandle); if (backgroundData != NULL) { buf_to_buf(backgroundData, windowDescription->width, windowDescription->height, windowDescription->width, windowBuffer, windowDescription->width); art_ptr_unlock(backgroundHandle); } MessageListItem messageListItem; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { // MOVE ITEMS messageListItem.num = 21; if (message_search(&inventry_message_file, &messageListItem)) { int length = text_width(messageListItem.text); text_to_buf(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, colorTable[21091]); } } else if (inventoryWindowType == INVENTORY_WINDOW_TYPE_SET_TIMER) { // SET TIMER messageListItem.num = 23; if (message_search(&inventry_message_file, &messageListItem)) { int length = text_width(messageListItem.text); text_to_buf(windowBuffer + windowDescription->width * 9 + (windowDescription->width - length) / 2, messageListItem.text, 200, windowDescription->width, colorTable[21091]); } // Timer overlay CacheEntry* overlayFrmHandle; int overlayFid = art_id(OBJ_TYPE_INTERFACE, 306, 0, 0, 0); unsigned char* overlayFrmData = art_ptr_lock_data(overlayFid, 0, 0, &overlayFrmHandle); if (overlayFrmData != NULL) { buf_to_buf(overlayFrmData, 105, 81, 105, windowBuffer + 34 * windowDescription->width + 113, windowDescription->width); art_ptr_unlock(overlayFrmHandle); } } int inventoryFid = item_inv_fid(item); scale_art(inventoryFid, windowBuffer + windowDescription->width * 46 + 16, INVENTORY_LARGE_SLOT_WIDTH, INVENTORY_LARGE_SLOT_HEIGHT, windowDescription->width); int x; int y; if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { x = 200; y = 46; } else { x = 194; y = 64; } int fid; unsigned char* buttonUpData; unsigned char* buttonDownData; int btn; // Plus button fid = art_id(OBJ_TYPE_INTERFACE, 193, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[0])); fid = art_id(OBJ_TYPE_INTERFACE, 194, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[1])); if (buttonUpData != NULL && buttonDownData != NULL) { btn = win_register_button(mt_wid, x, y, 16, 12, -1, -1, 6000, -1, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } // Minus button fid = art_id(OBJ_TYPE_INTERFACE, 191, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[2])); fid = art_id(OBJ_TYPE_INTERFACE, 192, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[3])); if (buttonUpData != NULL && buttonDownData != NULL) { btn = win_register_button(mt_wid, x, y + 12, 17, 12, -1, -1, 7000, -1, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[4])); fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[5])); if (buttonUpData != NULL && buttonDownData != NULL) { // Done btn = win_register_button(mt_wid, 98, 128, 15, 16, -1, -1, -1, KEY_RETURN, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // Cancel btn = win_register_button(mt_wid, 148, 128, 15, 16, -1, -1, -1, KEY_ESCAPE, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } if (inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS) { fid = art_id(OBJ_TYPE_INTERFACE, 307, 0, 0, 0); buttonUpData = art_ptr_lock_data(fid, 0, 0, &(mt_key[6])); fid = art_id(OBJ_TYPE_INTERFACE, 308, 0, 0, 0); buttonDownData = art_ptr_lock_data(fid, 0, 0, &(mt_key[7])); if (buttonUpData != NULL && buttonDownData != NULL) { // ALL messageListItem.num = 22; if (message_search(&inventry_message_file, &messageListItem)) { int length = text_width(messageListItem.text); // TODO: Where is y? Is it hardcoded in to 376? text_to_buf(buttonUpData + (94 - length) / 2 + 376, messageListItem.text, 200, 94, colorTable[21091]); text_to_buf(buttonDownData + (94 - length) / 2 + 376, messageListItem.text, 200, 94, colorTable[18977]); btn = win_register_button(mt_wid, 120, 80, 94, 33, -1, -1, -1, 5000, buttonUpData, buttonDownData, NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } } } } win_draw(mt_wid); inven_set_mouse(INVENTORY_WINDOW_CURSOR_ARROW); text_font(oldFont); return 0; } // 0x477030 static int exit_move_timer_win(int inventoryWindowType) { int count = inventoryWindowType == INVENTORY_WINDOW_TYPE_MOVE_ITEMS ? 8 : 6; for (int index = 0; index < count; index++) { art_ptr_unlock(mt_key[index]); } win_delete(mt_wid); return 0; } // 0x477074 int inven_set_timer(Object* a1) { bool v1 = inven_is_initialized; if (!v1) { if (inven_init() == -1) { return -1; } } int seconds = do_move_timer(INVENTORY_WINDOW_TYPE_SET_TIMER, a1, 180); if (!v1) { // NOTE: Uninline. inven_exit(); } return seconds; } ================================================ FILE: src/game/inventry.h ================================================ #ifndef FALLOUT_GAME_INVENTRY_H_ #define FALLOUT_GAME_INVENTRY_H_ #include #include "game/art.h" #include "game/object_types.h" // TODO: Convert to enum. #define OFF_59E7BC_COUNT 12 typedef enum InventoryWindowCursor { INVENTORY_WINDOW_CURSOR_HAND, INVENTORY_WINDOW_CURSOR_ARROW, INVENTORY_WINDOW_CURSOR_PICK, INVENTORY_WINDOW_CURSOR_MENU, INVENTORY_WINDOW_CURSOR_BLANK, INVENTORY_WINDOW_CURSOR_COUNT, } InventoryWindowCursor; typedef enum InventoryWindowType { // Normal inventory window with quick character sheet. INVENTORY_WINDOW_TYPE_NORMAL, // Narrow inventory window with just an item scroller that's shown when // a "Use item on" is selected from context menu. INVENTORY_WINDOW_TYPE_USE_ITEM_ON, // Looting/strealing interface. INVENTORY_WINDOW_TYPE_LOOT, // Barter interface. INVENTORY_WINDOW_TYPE_TRADE, // Supplementary "Move items" window. Used to set quantity of items when // moving items between inventories. INVENTORY_WINDOW_TYPE_MOVE_ITEMS, // Supplementary "Set timer" window. Internally it's implemented as "Move // items" window but with timer overlay and slightly different adjustment // mechanics. INVENTORY_WINDOW_TYPE_SET_TIMER, INVENTORY_WINDOW_TYPE_COUNT, } InventoryWindowType; extern CacheEntry* ikey[OFF_59E7BC_COUNT]; void inven_set_dude(Object* obj, int pid); void inven_reset_dude(); void handle_inventory(); bool setup_inventory(int inventoryWindowType); void exit_inventory(bool a1); void display_inventory(int a1, int a2, int inventoryWindowType); void display_target_inventory(int a1, int a2, Inventory* a3, int a4); void display_body(int fid, int inventoryWindowType); int inven_init(); void inven_exit(); void inven_set_mouse(int cursor); void inven_hover_on(int btn, int keyCode); void inven_hover_off(int btn, int keyCode); void inven_pickup(int keyCode, int a2); void switch_hand(Object* a1, Object** a2, Object** a3, int a4); void adjust_ac(Object* critter, Object* oldArmor, Object* newArmor); void adjust_fid(); void use_inventory_on(Object* a1); Object* inven_right_hand(Object* obj); Object* inven_left_hand(Object* obj); Object* inven_worn(Object* obj); Object* inven_pid_is_carried(Object* obj, int pid); int inven_pid_quantity_carried(Object* obj, int pid); void display_stats(); Object* inven_find_type(Object* obj, int a2, int* inout_a3); Object* inven_find_id(Object* obj, int a2); Object* inven_index_ptr(Object* obj, int a2); int inven_wield(Object* a1, Object* a2, int a3); int invenWieldFunc(Object* a1, Object* a2, int a3, bool a4); int inven_unwield(Object* critter_obj, int a2); int invenUnwieldFunc(Object* obj, int a2, int a3); int inven_from_button(int a1, Object** a2, Object*** a3, Object** a4); void inven_display_msg(char* string); void inven_obj_examine_func(Object* critter, Object* item); void inven_action_cursor(int eventCode, int inventoryWindowType); int loot_container(Object* a1, Object* a2); int inven_steal_container(Object* a1, Object* a2); int move_inventory(Object* a1, int a2, Object* a3, bool a4); void barter_inventory(int win, Object* a2, Object* a3, Object* a4, int a5); void container_enter(int a1, int a2); void container_exit(int keyCode, int inventoryWindowType); int drop_into_container(Object* a1, Object* a2, int a3, Object** a4, int quantity); int drop_ammo_into_weapon(Object* weapon, Object* ammo, Object** a3, int quantity, int keyCode); void draw_amount(int value, int inventoryWindowType); int inven_set_timer(Object* a1); #endif /* FALLOUT_GAME_INVENTRY_H_ */ ================================================ FILE: src/game/item.c ================================================ #include "game/item.h" #include #include "game/anim.h" #include "game/automap.h" #include "game/combat.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/intface.h" #include "game/inventry.h" #include "game/light.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/protinst.h" #include "game/queue.h" #include "game/roll.h" #include "game/skill.h" #include "game/stat.h" #include "game/tile.h" #include "game/trait.h" static void item_compact(int inventoryItemIndex, Inventory* inventory); static int item_move_func(Object* a1, Object* a2, Object* a3, int quantity, bool a5); static bool item_identical(Object* a1, Object* a2); static int item_m_stealth_effect_on(Object* object); static int item_m_stealth_effect_off(Object* critter, Object* item); static int insert_drug_effect(Object* critter_obj, Object* item_obj, int a3, int* stats, int* mods); static void perform_drug_effect(Object* critter_obj, int* stats, int* mods, bool is_immediate); static bool drug_effect_allowed(Object* critter, int pid); static int insert_withdrawal(Object* obj, int a2, int a3, int a4, int a5); static int item_wd_clear_all(Object* a1, void* data); static void perform_withdrawal_start(Object* obj, int perk, int a3); static void perform_withdrawal_end(Object* obj, int a2); static int pid_to_gvar(int drugPid); // TODO: Remove. // 0x509FFC char _aItem_1[] = ""; // Maps weapon extended flags to skill. // // 0x519160 static int attack_skill[9] = { -1, SKILL_UNARMED, SKILL_UNARMED, SKILL_MELEE_WEAPONS, SKILL_MELEE_WEAPONS, SKILL_THROWING, SKILL_SMALL_GUNS, SKILL_SMALL_GUNS, SKILL_SMALL_GUNS, }; // A map of item's extendedFlags to animation. // // 0x519184 static int attack_anim[9] = { ANIM_STAND, ANIM_THROW_PUNCH, ANIM_KICK_LEG, ANIM_SWING_ANIM, ANIM_THRUST_ANIM, ANIM_THROW_ANIM, ANIM_FIRE_SINGLE, ANIM_FIRE_BURST, ANIM_FIRE_CONTINUOUS, }; // Maps weapon extended flags to weapon class // // 0x5191A8 static int attack_subtype[9] = { ATTACK_TYPE_NONE, // 0 // None ATTACK_TYPE_UNARMED, // 1 // Punch // Brass Knuckles, Power First ATTACK_TYPE_UNARMED, // 2 // Kick? ATTACK_TYPE_MELEE, // 3 // Swing // Sledgehammer (prim), Club, Knife (prim), Spear (prim), Crowbar ATTACK_TYPE_MELEE, // 4 // Thrust // Sledgehammer (sec), Knife (sec), Spear (sec) ATTACK_TYPE_THROW, // 5 // Throw // Rock, ATTACK_TYPE_RANGED, // 6 // Single // 10mm SMG (prim), Rocket Launcher, Hunting Rifle, Plasma Rifle, Laser Pistol ATTACK_TYPE_RANGED, // 7 // Burst // 10mm SMG (sec), Minigun ATTACK_TYPE_RANGED, // 8 // Continous // Only: Flamer, Improved Flamer, Flame Breath }; // 0x5191CC DrugDescription drugInfoList[ADDICTION_COUNT] = { { PROTO_ID_NUKA_COLA, GVAR_NUKA_COLA_ADDICT, 0 }, { PROTO_ID_BUFF_OUT, GVAR_BUFF_OUT_ADDICT, 4 }, { PROTO_ID_MENTATS, GVAR_MENTATS_ADDICT, 4 }, { PROTO_ID_PSYCHO, GVAR_PSYCHO_ADDICT, 4 }, { PROTO_ID_RADAWAY, GVAR_RADAWAY_ADDICT, 0 }, { PROTO_ID_BEER, GVAR_ALCOHOL_ADDICT, 0 }, { PROTO_ID_BOOZE, GVAR_ALCOHOL_ADDICT, 0 }, { PROTO_ID_JET, GVAR_ADDICT_JET, 4 }, { PROTO_ID_DECK_OF_TRAGIC_CARDS, GVAR_ADDICT_TRAGIC, 0 }, }; // item.msg // // 0x59E980 static MessageList item_message_file; // 0x59E988 static int wd_onset; // 0x59E98C static Object* wd_obj; // 0x59E990 static int wd_gvar; // 0x4770E0 int item_init() { if (!message_init(&item_message_file)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "item.msg"); if (!message_load(&item_message_file, path)) { return -1; } return 0; } // 0x477144 void item_reset() { return; } // 0x477148 void item_exit() { message_exit(&item_message_file); } // NOTE: Uncollapsed 0x477154. int item_load(File* stream) { return 0; } // NOTE: Uncollapsed 0x477154. int item_save(File* stream) { return 0; } // 0x477158 int item_add_mult(Object* owner, Object* itemToAdd, int quantity) { if (quantity < 1) { return -1; } int parentType = FID_TYPE(owner->fid); if (parentType == OBJ_TYPE_ITEM) { int itemType = item_get_type(owner); if (itemType == ITEM_TYPE_CONTAINER) { // NOTE: Uninline. int sizeToAdd = item_size(itemToAdd); sizeToAdd *= quantity; int currentSize = item_c_curr_size(owner); int maxSize = item_c_max_size(owner); if (currentSize + sizeToAdd >= maxSize) { return -6; } Object* containerOwner = obj_top_environment(owner); if (containerOwner != NULL) { if (FID_TYPE(containerOwner->fid) == OBJ_TYPE_CRITTER) { int weightToAdd = item_weight(itemToAdd); weightToAdd *= quantity; int currentWeight = item_total_weight(containerOwner); int maxWeight = critterGetStat(containerOwner, STAT_CARRY_WEIGHT); if (currentWeight + weightToAdd > maxWeight) { return -6; } } } } else if (itemType == ITEM_TYPE_MISC) { // NOTE: Uninline. int powerTypePid = item_m_cell_pid(owner); if (powerTypePid != itemToAdd->pid) { return -1; } } else { return -1; } } else if (parentType == OBJ_TYPE_CRITTER) { if (critter_body_type(owner) != BODY_TYPE_BIPED) { return -5; } int weightToAdd = item_weight(itemToAdd); weightToAdd *= quantity; int currentWeight = item_total_weight(owner); int maxWeight = critterGetStat(owner, STAT_CARRY_WEIGHT); if (currentWeight + weightToAdd > maxWeight) { return -6; } } return item_add_force(owner, itemToAdd, quantity); } // item_add // 0x4772B8 int item_add_force(Object* owner, Object* itemToAdd, int quantity) { if (quantity < 1) { return -1; } Inventory* inventory = &(owner->data.inventory); int index; for (index = 0; index < inventory->length; index++) { if (item_identical(inventory->items[index].item, itemToAdd) != 0) { break; } } if (index == inventory->length) { if (inventory->length == inventory->capacity || inventory->items == NULL) { InventoryItem* inventoryItems = (InventoryItem*)mem_realloc(inventory->items, sizeof(InventoryItem) * (inventory->capacity + 10)); if (inventoryItems == NULL) { return -1; } inventory->items = inventoryItems; inventory->capacity += 10; } inventory->items[inventory->length].item = itemToAdd; inventory->items[inventory->length].quantity = quantity; if (itemToAdd->pid == PROTO_ID_STEALTH_BOY_II) { if ((itemToAdd->flags & OBJECT_IN_ANY_HAND) != 0) { // NOTE: Uninline. item_m_stealth_effect_on(owner); } } inventory->length++; itemToAdd->owner = owner; return 0; } if (itemToAdd == inventory->items[index].item) { debug_printf("Warning! Attempt to add same item twice in item_add()\n"); return 0; } if (item_get_type(itemToAdd) == ITEM_TYPE_AMMO) { // NOTE: Uninline. int ammoQuantityToAdd = item_w_curr_ammo(itemToAdd); int ammoQuantity = item_w_curr_ammo(inventory->items[index].item); // NOTE: Uninline. int capacity = item_w_max_ammo(itemToAdd); ammoQuantity += ammoQuantityToAdd; if (ammoQuantity > capacity) { item_w_set_curr_ammo(itemToAdd, ammoQuantity - capacity); inventory->items[index].quantity++; } else { item_w_set_curr_ammo(itemToAdd, ammoQuantity); } inventory->items[index].quantity += quantity - 1; } else { inventory->items[index].quantity += quantity; } obj_erase_object(inventory->items[index].item, NULL); inventory->items[index].item = itemToAdd; itemToAdd->owner = owner; return 0; } // 0x477490 int item_remove_mult(Object* owner, Object* itemToRemove, int quantity) { Inventory* inventory = &(owner->data.inventory); Object* item1 = inven_left_hand(owner); Object* item2 = inven_right_hand(owner); int index = 0; for (; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->item == itemToRemove) { break; } if (item_get_type(inventoryItem->item) == ITEM_TYPE_CONTAINER) { if (item_remove_mult(inventoryItem->item, itemToRemove, quantity) == 0) { return 0; } } } if (index == inventory->length) { return -1; } InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->quantity < quantity) { return -1; } if (inventoryItem->quantity == quantity) { // NOTE: Uninline. item_compact(index, inventory); } else { // TODO: Not sure about this line. if (obj_copy(&(inventoryItem->item), itemToRemove) == -1) { return -1; } obj_disconnect(inventoryItem->item, NULL); inventoryItem->quantity -= quantity; if (item_get_type(itemToRemove) == ITEM_TYPE_AMMO) { int capacity = item_w_max_ammo(itemToRemove); item_w_set_curr_ammo(inventoryItem->item, capacity); } } if (itemToRemove->pid == PROTO_ID_STEALTH_BOY_I || itemToRemove->pid == PROTO_ID_STEALTH_BOY_II) { if (itemToRemove == item1 || itemToRemove == item2) { Object* owner = obj_top_environment(itemToRemove); if (owner != NULL) { item_m_stealth_effect_off(owner, itemToRemove); } } } itemToRemove->owner = NULL; itemToRemove->flags &= ~OBJECT_EQUIPPED; return 0; } // NOTE: Inlined. // // 0x4775D8 static void item_compact(int inventoryItemIndex, Inventory* inventory) { for (int index = inventoryItemIndex + 1; index < inventory->length; index++) { InventoryItem* prev = &(inventory->items[index - 1]); InventoryItem* curr = &(inventory->items[index]); static_assert(sizeof(*prev) == sizeof(*curr), "wrong size"); memcpy(prev, curr, sizeof(*prev)); } inventory->length--; } // 0x477608 static int item_move_func(Object* a1, Object* a2, Object* a3, int quantity, bool a5) { if (item_remove_mult(a1, a3, quantity) == -1) { return -1; } int rc; if (a5) { rc = item_add_force(a2, a3, quantity); } else { rc = item_add_mult(a2, a3, quantity); } if (rc != 0) { if (item_add_force(a1, a3, quantity) != 0) { Object* owner = obj_top_environment(a1); if (owner == NULL) { owner = a1; } if (owner->tile != -1) { Rect updatedRect; obj_connect(a3, owner->tile, owner->elevation, &updatedRect); tile_refresh_rect(&updatedRect, map_elevation); } } return -1; } a3->owner = a2; return 0; } // 0x47769C int item_move(Object* a1, Object* a2, Object* a3, int quantity) { return item_move_func(a1, a2, a3, quantity, false); } // 0x4776A4 int item_move_force(Object* a1, Object* a2, Object* a3, int quantity) { return item_move_func(a1, a2, a3, quantity, true); } // 0x4776AC void item_move_all(Object* a1, Object* a2) { Inventory* inventory = &(a1->data.inventory); while (inventory->length > 0) { InventoryItem* inventoryItem = &(inventory->items[0]); item_move_func(a1, a2, inventoryItem->item, inventoryItem->quantity, true); } } // 0x4776E0 int item_move_all_hidden(Object* a1, Object* a2) { Inventory* inventory = &(a1->data.inventory); // TODO: Not sure about two loops. for (int i = 0; i < inventory->length;) { for (int j = i; j < inventory->length;) { bool v5; InventoryItem* inventoryItem = &(inventory->items[j]); if (PID_TYPE(inventoryItem->item->pid) == OBJ_TYPE_ITEM) { Proto* proto; if (proto_ptr(inventoryItem->item->pid, &proto) != -1) { v5 = (proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon) == 0; } else { v5 = true; } } else { v5 = true; } if (!v5) { item_move_func(a1, a2, inventoryItem->item, inventoryItem->quantity, true); } else { i++; j++; } } } return 0; } // 0x477770 int item_destroy_all_hidden(Object* a1) { Inventory* inventory = &(a1->data.inventory); // TODO: Not sure about this one. Why two loops? for (int i = 0; i < inventory->length;) { // TODO: Probably wrong, something with two loops. for (int j = i; j < inventory->length;) { bool v5; InventoryItem* inventoryItem = &(inventory->items[j]); if (PID_TYPE(inventoryItem->item->pid) == OBJ_TYPE_ITEM) { Proto* proto; if (proto_ptr(inventoryItem->item->pid, &proto) != -1) { v5 = (proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon) == 0; } else { v5 = true; } } else { v5 = true; } if (!v5) { item_remove_mult(a1, inventoryItem->item, 1); obj_destroy(inventoryItem->item); } else { i++; j++; } } } return 0; } // 0x477804 int item_drop_all(Object* critter, int tile) { bool hasEquippedItems = false; int frmId = critter->fid & 0xFFF; Inventory* inventory = &(critter->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item->pid == PROTO_ID_MONEY) { if (item_remove_mult(critter, item, inventoryItem->quantity) != 0) { return -1; } if (obj_connect(item, tile, critter->elevation, NULL) != 0) { if (item_add_force(critter, item, 1) != 0) { obj_destroy(item); } return -1; } item->data.item.misc.charges = inventoryItem->quantity; } else { if ((item->flags & OBJECT_EQUIPPED) != 0) { hasEquippedItems = true; if ((item->flags & OBJECT_WORN) != 0) { Proto* proto; if (proto_ptr(critter->pid, &proto) == -1) { return -1; } frmId = proto->fid & 0xFFF; adjust_ac(critter, item, NULL); } } for (int index = 0; index < inventoryItem->quantity; index++) { if (item_remove_mult(critter, item, 1) != 0) { return -1; } if (obj_connect(item, tile, critter->elevation, NULL) != 0) { if (item_add_force(critter, item, 1) != 0) { obj_destroy(item); } return -1; } } } } if (hasEquippedItems) { Rect updatedRect; int fid = art_id(OBJ_TYPE_CRITTER, frmId, FID_ANIM_TYPE(critter->fid), 0, (critter->fid & 0x70000000) >> 28); obj_change_fid(critter, fid, &updatedRect); if (FID_ANIM_TYPE(critter->fid) == ANIM_STAND) { tile_refresh_rect(&updatedRect, map_elevation); } } return -1; } // 0x4779F0 static bool item_identical(Object* a1, Object* a2) { if (a1->pid != a2->pid) { return false; } if (a1->sid != a2->sid) { return false; } if ((a1->flags & (OBJECT_EQUIPPED | OBJECT_USED)) != 0) { return false; } if ((a2->flags & (OBJECT_EQUIPPED | OBJECT_USED)) != 0) { return false; } Proto* proto; proto_ptr(a1->pid, &proto); if (proto->item.type == ITEM_TYPE_CONTAINER) { return false; } Inventory* inventory1 = &(a1->data.inventory); Inventory* inventory2 = &(a2->data.inventory); if (inventory1->length != 0 || inventory2->length != 0) { return false; } int v1; if (proto->item.type == ITEM_TYPE_AMMO || a1->pid == PROTO_ID_MONEY) { v1 = a2->data.item.ammo.quantity; a2->data.item.ammo.quantity = a1->data.item.ammo.quantity; } // NOTE: Probably inlined memcmp, but I'm not sure why it only checks 32 // bytes. int i; for (i = 0; i < 8; i++) { if (a1->field_2C_array[i] != a2->field_2C_array[i]) { break; } } if (proto->item.type == ITEM_TYPE_AMMO || a1->pid == PROTO_ID_MONEY) { a2->data.item.ammo.quantity = v1; } return i == 8; } // 0x477AE4 char* item_name(Object* obj) { // 0x519238 static char* name = _aItem_1; name = proto_name(obj->pid); return name; } // 0x477AF4 char* item_description(Object* obj) { return proto_description(obj->pid); } // 0x477AFC int item_get_type(Object* item) { if (item == NULL) { return ITEM_TYPE_MISC; } if (PID_TYPE(item->pid) != OBJ_TYPE_ITEM) { return ITEM_TYPE_MISC; } if (item->pid == PROTO_ID_SHIV) { return ITEM_TYPE_MISC; } Proto* proto; proto_ptr(item->pid, &proto); return proto->item.type; } // NOTE: Unused. // // 0x477B4C int item_material(Object* item) { Proto* proto; proto_ptr(item->pid, &proto); return proto->item.material; } // 0x477B68 int item_size(Object* item) { if (item == NULL) { return 0; } Proto* proto; proto_ptr(item->pid, &proto); return proto->item.size; } // 0x477B88 int item_weight(Object* item) { if (item == NULL) { return 0; } Proto* proto; proto_ptr(item->pid, &proto); int weight = proto->item.weight; // NOTE: Uninline. if (item_is_hidden(item)) { weight = 0; } int itemType = proto->item.type; if (itemType == ITEM_TYPE_ARMOR) { switch (proto->pid) { case PROTO_ID_POWER_ARMOR: case PROTO_ID_HARDENED_POWER_ARMOR: case PROTO_ID_ADVANCED_POWER_ARMOR: case PROTO_ID_ADVANCED_POWER_ARMOR_MK_II: weight /= 2; break; } } else if (itemType == ITEM_TYPE_CONTAINER) { weight += item_total_weight(item); } else if (itemType == ITEM_TYPE_WEAPON) { // NOTE: Uninline. int ammoQuantity = item_w_curr_ammo(item); if (ammoQuantity > 0) { // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(item); if (ammoTypePid != -1) { Proto* ammoProto; if (proto_ptr(ammoTypePid, &ammoProto) != -1) { weight += ammoProto->item.weight * ((ammoQuantity - 1) / ammoProto->item.data.ammo.quantity + 1); } } } } return weight; } // Returns cost of item. // // When [item] is container the returned cost includes cost of container // itself plus cost of contained items. // // When [item] is a weapon the returned value includes cost of weapon // itself plus cost of remaining ammo (see below). // // When [item] is an ammo it's cost is calculated from ratio of fullness. // // 0x477CAC int item_cost(Object* obj) { // TODO: This function needs review. A lot of functionality is inlined. // Find these functions and use them. if (obj == NULL) { return 0; } Proto* proto; proto_ptr(obj->pid, &proto); int cost = proto->item.cost; switch (proto->item.type) { case ITEM_TYPE_CONTAINER: cost += item_total_cost(obj); break; case ITEM_TYPE_WEAPON: if (1) { // NOTE: Uninline. int ammoQuantity = item_w_curr_ammo(obj); if (ammoQuantity > 0) { // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(obj); if (ammoTypePid != -1) { Proto* ammoProto; proto_ptr(ammoTypePid, &ammoProto); cost += ammoQuantity * ammoProto->item.cost / ammoProto->item.data.ammo.quantity; } } } break; case ITEM_TYPE_AMMO: if (1) { // NOTE: Uninline. int ammoQuantity = item_w_curr_ammo(obj); cost *= ammoQuantity; // NOTE: Uninline. int ammoCapacity = item_w_max_ammo(obj); cost /= ammoCapacity; } break; } return cost; } // Returns cost of object's items. // // 0x477DAC int item_total_cost(Object* obj) { if (obj == NULL) { return 0; } int cost = 0; Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (item_get_type(inventoryItem->item) == ITEM_TYPE_AMMO) { Proto* proto; proto_ptr(inventoryItem->item->pid, &proto); // Ammo stack in inventory is a bit special. It is counted in clips, // `inventoryItem->quantity` is the number of clips. The ammo object // itself tracks remaining number of ammo in only one instance of // the clip implying all other clips in the stack are full. // // In order to correctly calculate cost of the ammo stack, add cost // of all full clips... cost += proto->item.cost * (inventoryItem->quantity - 1); // ...and add cost of the current clip, which is proportional to // it's capacity. cost += item_cost(inventoryItem->item); } else { cost += item_cost(inventoryItem->item) * inventoryItem->quantity; } } if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) { Object* item2 = inven_right_hand(obj); if (item2 != NULL && (item2->flags & OBJECT_IN_RIGHT_HAND) == 0) { cost += item_cost(item2); } Object* item1 = inven_left_hand(obj); if (item1 != NULL && (item1->flags & OBJECT_IN_LEFT_HAND) == 0) { cost += item_cost(item1); } Object* armor = inven_worn(obj); if (armor != NULL && (armor->flags & OBJECT_WORN) == 0) { cost += item_cost(armor); } } return cost; } // Calculates total weight of the items in inventory. // // 0x477E98 int item_total_weight(Object* obj) { if (obj == NULL) { return 0; } int weight = 0; Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; weight += item_weight(item) * inventoryItem->quantity; } if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) { Object* item2 = inven_right_hand(obj); if (item2 != NULL) { if ((item2->flags & OBJECT_IN_RIGHT_HAND) == 0) { weight += item_weight(item2); } } Object* item1 = inven_left_hand(obj); if (item1 != NULL) { if ((item1->flags & OBJECT_IN_LEFT_HAND) == 0) { weight += item_weight(item1); } } Object* armor = inven_worn(obj); if (armor != NULL) { if ((armor->flags & OBJECT_WORN) == 0) { weight += item_weight(armor); } } } return weight; } // 0x477F3C bool item_grey(Object* weapon) { if (weapon == NULL) { return false; } if (item_get_type(weapon) != ITEM_TYPE_WEAPON) { return false; } int flags = obj_dude->data.critter.combat.results; if ((flags & DAM_CRIP_ARM_LEFT) != 0 && (flags & DAM_CRIP_ARM_RIGHT) != 0) { return true; } // NOTE: Uninline. bool isTwoHanded = item_w_is_2handed(weapon); if (isTwoHanded) { if ((flags & DAM_CRIP_ARM_LEFT) != 0 || (flags & DAM_CRIP_ARM_RIGHT) != 0) { return true; } } return false; } // 0x477FB0 int item_inv_fid(Object* item) { Proto* proto; if (item == NULL) { return -1; } proto_ptr(item->pid, &proto); return proto->item.inventoryFid; } // 0x477FF8 Object* item_hit_with(Object* critter, int hitMode) { switch (hitMode) { case HIT_MODE_LEFT_WEAPON_PRIMARY: case HIT_MODE_LEFT_WEAPON_SECONDARY: case HIT_MODE_LEFT_WEAPON_RELOAD: return inven_left_hand(critter); case HIT_MODE_RIGHT_WEAPON_PRIMARY: case HIT_MODE_RIGHT_WEAPON_SECONDARY: case HIT_MODE_RIGHT_WEAPON_RELOAD: return inven_right_hand(critter); } return NULL; } // 0x478040 int item_mp_cost(Object* obj, int hitMode, bool aiming) { if (obj == NULL) { return 0; } Object* item_obj = item_hit_with(obj, hitMode); if (item_obj != NULL && item_get_type(item_obj) != ITEM_TYPE_WEAPON) { return 2; } return item_w_mp_cost(obj, hitMode, aiming); } // Returns quantity of [a2] in [obj]s inventory. // // 0x47808C int item_count(Object* obj, Object* a2) { int quantity = 0; Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item == a2) { quantity = inventoryItem->quantity; } else { if (item_get_type(item) == ITEM_TYPE_CONTAINER) { quantity = item_count(item, a2); if (quantity > 0) { return quantity; } } } } return quantity; } // Returns true if [a1] posesses an item with 0x2000 flag. // // 0x4780E4 int item_queued(Object* obj) { if (obj == NULL) { return false; } if ((obj->flags & OBJECT_USED) != 0) { return true; } Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if ((inventoryItem->item->flags & OBJECT_USED) != 0) { return true; } if (item_get_type(inventoryItem->item) == ITEM_TYPE_CONTAINER) { if (item_queued(inventoryItem->item)) { return true; } } } return false; } // 0x478154 Object* item_replace(Object* a1, Object* a2, int a3) { if (a1 == NULL) { return NULL; } if (a2 == NULL) { return NULL; } Inventory* inventory = &(a1->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (item_identical(inventoryItem->item, a2)) { Object* item = inventoryItem->item; if (item_remove_mult(a1, item, 1) == 0) { item->flags |= a3; if (item_add_force(a1, item, 1) == 0) { return item; } item->flags &= ~a3; if (item_add_force(a1, item, 1) != 0) { obj_destroy(item); } } } if (item_get_type(inventoryItem->item) == ITEM_TYPE_CONTAINER) { Object* obj = item_replace(inventoryItem->item, a2, a3); if (obj != NULL) { return obj; } } } return NULL; } // Returns true if [item] is an natural weapon of it's owner. // // See [ItemProtoExtendedFlags_NaturalWeapon] for more details on natural weapons. // // 0x478244 int item_is_hidden(Object* obj) { Proto* proto; if (PID_TYPE(obj->pid) != OBJ_TYPE_ITEM) { return 0; } if (proto_ptr(obj->pid, &proto) == -1) { return 0; } return proto->item.extendedFlags & ItemProtoExtendedFlags_NaturalWeapon; } // 0x478280 int item_w_subtype(Object* weapon, int hitMode) { if (weapon == NULL) { return ATTACK_TYPE_UNARMED; } Proto* proto; proto_ptr(weapon->pid, &proto); int index; if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) { index = proto->item.extendedFlags & 0xF; } else { index = (proto->item.extendedFlags & 0xF0) >> 4; } return attack_subtype[index]; } // 0x4782CC int item_w_skill(Object* weapon, int hitMode) { if (weapon == NULL) { return SKILL_UNARMED; } Proto* proto; proto_ptr(weapon->pid, &proto); int index; if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) { index = proto->item.extendedFlags & 0xF; } else { index = (proto->item.extendedFlags & 0xF0) >> 4; } int skill = attack_skill[index]; if (skill == SKILL_SMALL_GUNS) { int damageType = item_w_damage_type(NULL, weapon); if (damageType == DAMAGE_TYPE_LASER || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_ELECTRICAL) { skill = SKILL_ENERGY_WEAPONS; } else { if ((proto->item.extendedFlags & ItemProtoExtendedFlags_BigGun) != 0) { skill = SKILL_BIG_GUNS; } } } return skill; } // Returns skill value when critter is about to perform hitMode. // // 0x478370 int item_w_skill_level(Object* critter, int hitMode) { if (critter == NULL) { return 0; } int skill; // NOTE: Uninline. Object* weapon = item_hit_with(critter, hitMode); if (weapon != NULL) { skill = item_w_skill(weapon, hitMode); } else { skill = SKILL_UNARMED; } return skill_level(critter, skill); } // 0x4783B8 int item_w_damage_min_max(Object* weapon, int* minDamagePtr, int* maxDamagePtr) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); if (minDamagePtr != NULL) { *minDamagePtr = proto->item.data.weapon.minDamage; } if (maxDamagePtr != NULL) { *maxDamagePtr = proto->item.data.weapon.maxDamage; } return 0; } // 0x478448 int item_w_damage(Object* critter, int hitMode) { if (critter == NULL) { return 0; } int minDamage = 0; int maxDamage = 0; int meleeDamage = 0; int unarmedDamage = 0; // NOTE: Uninline. Object* weapon = item_hit_with(critter, hitMode); if (weapon != NULL) { Proto* proto; proto_ptr(weapon->pid, &proto); minDamage = proto->item.data.weapon.minDamage; maxDamage = proto->item.data.weapon.maxDamage; int attackType = item_w_subtype(weapon, hitMode); if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { meleeDamage = critterGetStat(critter, STAT_MELEE_DAMAGE); } } else { minDamage = 1; maxDamage = critterGetStat(critter, STAT_MELEE_DAMAGE) + 2; switch (hitMode) { case HIT_MODE_STRONG_PUNCH: case HIT_MODE_JAB: unarmedDamage = 3; break; case HIT_MODE_HAMMER_PUNCH: case HIT_MODE_STRONG_KICK: unarmedDamage = 4; break; case HIT_MODE_HAYMAKER: case HIT_MODE_PALM_STRIKE: case HIT_MODE_SNAP_KICK: case HIT_MODE_HIP_KICK: unarmedDamage = 7; break; case HIT_MODE_POWER_KICK: case HIT_MODE_HOOK_KICK: unarmedDamage = 9; break; case HIT_MODE_PIERCING_STRIKE: unarmedDamage = 10; break; case HIT_MODE_PIERCING_KICK: unarmedDamage = 12; break; } } return roll_random(unarmedDamage + minDamage, unarmedDamage + meleeDamage + maxDamage); } // 0x478570 int item_w_damage_type(Object* critter, Object* weapon) { Proto* proto; if (weapon != NULL) { proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.damageType; } if (critter != NULL) { return critter_get_base_damage_type(critter); } return 0; } // 0x478598 int item_w_is_2handed(Object* weapon) { Proto* proto; if (weapon == NULL) { return 0; } proto_ptr(weapon->pid, &proto); return (proto->item.extendedFlags & WEAPON_TWO_HAND) != 0; } // 0x4785DC int item_w_anim(Object* critter, int hitMode) { // NOTE: Uninline. Object* weapon = item_hit_with(critter, hitMode); return item_w_anim_weap(weapon, hitMode); } // 0x47860C int item_w_anim_weap(Object* weapon, int hitMode) { if (hitMode == HIT_MODE_KICK || (hitMode >= FIRST_ADVANCED_KICK_HIT_MODE && hitMode <= LAST_ADVANCED_KICK_HIT_MODE)) { return ANIM_KICK_LEG; } if (weapon == NULL) { return ANIM_THROW_PUNCH; } Proto* proto; proto_ptr(weapon->pid, &proto); int index; if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) { index = proto->item.extendedFlags & 0xF; } else { index = (proto->item.extendedFlags & 0xF0) >> 4; } return attack_anim[index]; } // 0x478674 int item_w_max_ammo(Object* ammoOrWeapon) { if (ammoOrWeapon == NULL) { return 0; } Proto* proto; proto_ptr(ammoOrWeapon->pid, &proto); if (proto->item.type == ITEM_TYPE_AMMO) { return proto->item.data.ammo.quantity; } else { return proto->item.data.weapon.ammoCapacity; } } // 0x4786A0 int item_w_curr_ammo(Object* ammoOrWeapon) { if (ammoOrWeapon == NULL) { return 0; } Proto* proto; proto_ptr(ammoOrWeapon->pid, &proto); // NOTE: Looks like the condition jumps were erased during compilation only // because ammo's quantity and weapon's ammo quantity coincidently stored // in the same offset relative to [Object]. if (proto->item.type == ITEM_TYPE_AMMO) { return ammoOrWeapon->data.item.ammo.quantity; } else { return ammoOrWeapon->data.item.weapon.ammoQuantity; } } // 0x4786C8 int item_w_caliber(Object* ammoOrWeapon) { Proto* proto; if (ammoOrWeapon == NULL) { return 0; } proto_ptr(ammoOrWeapon->pid, &proto); if (proto->item.type != ITEM_TYPE_AMMO) { if (proto_ptr(ammoOrWeapon->data.item.weapon.ammoTypePid, &proto) == -1) { return 0; } } return proto->item.data.ammo.caliber; } // 0x478714 void item_w_set_curr_ammo(Object* ammoOrWeapon, int quantity) { if (ammoOrWeapon == NULL) { return; } // NOTE: Uninline. int capacity = item_w_max_ammo(ammoOrWeapon); if (quantity > capacity) { quantity = capacity; } Proto* proto; proto_ptr(ammoOrWeapon->pid, &proto); if (proto->item.type == ITEM_TYPE_AMMO) { ammoOrWeapon->data.item.ammo.quantity = quantity; } else { ammoOrWeapon->data.item.weapon.ammoQuantity = quantity; } } // 0x478768 int item_w_try_reload(Object* critter, Object* weapon) { // NOTE: Uninline. int quantity = item_w_curr_ammo(weapon); int capacity = item_w_max_ammo(weapon); if (quantity == capacity) { return -1; } if (weapon->pid != PROTO_ID_SOLAR_SCORCHER) { int inventoryItemIndex = -1; for (;;) { Object* ammo = inven_find_type(critter, ITEM_TYPE_AMMO, &inventoryItemIndex); if (ammo == NULL) { break; } if (weapon->data.item.weapon.ammoTypePid == ammo->pid) { if (item_w_can_reload(weapon, ammo) != 0) { int rc = item_w_reload(weapon, ammo); if (rc == 0) { obj_destroy(ammo); } if (rc == -1) { return -1; } return 0; } } } inventoryItemIndex = -1; for (;;) { Object* ammo = inven_find_type(critter, ITEM_TYPE_AMMO, &inventoryItemIndex); if (ammo == NULL) { break; } if (item_w_can_reload(weapon, ammo) != 0) { int rc = item_w_reload(weapon, ammo); if (rc == 0) { obj_destroy(ammo); } if (rc == -1) { return -1; } return 0; } } } if (item_w_reload(weapon, NULL) != 0) { return -1; } return 0; } // Checks if weapon can be reloaded with the specified ammo. // // 0x478874 bool item_w_can_reload(Object* weapon, Object* ammo) { if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { // Check light level to recharge solar scorcher. if (light_get_ambient() > 62259) { return true; } // There is not enough light to recharge this item. MessageListItem messageListItem; char* msg = getmsg(&item_message_file, &messageListItem, 500); display_print(msg); return false; } if (ammo == NULL) { return false; } Proto* weaponProto; proto_ptr(weapon->pid, &weaponProto); Proto* ammoProto; proto_ptr(ammo->pid, &ammoProto); if (weaponProto->item.type != ITEM_TYPE_WEAPON) { return false; } if (ammoProto->item.type != ITEM_TYPE_AMMO) { return false; } // Check ammo matches weapon caliber. if (weaponProto->item.data.weapon.caliber != ammoProto->item.data.ammo.caliber) { return false; } // If weapon is not empty, we should only reload it with the same ammo. if (item_w_curr_ammo(weapon) != 0) { if (weapon->data.item.weapon.ammoTypePid != ammo->pid) { return false; } } return true; } // 0x478918 int item_w_reload(Object* weapon, Object* ammo) { if (!item_w_can_reload(weapon, ammo)) { return -1; } // NOTE: Uninline. int ammoQuantity = item_w_curr_ammo(weapon); // NOTE: Uninline. int ammoCapacity = item_w_max_ammo(weapon); if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { item_w_set_curr_ammo(weapon, ammoCapacity); return 0; } // NOTE: Uninline. int v10 = item_w_curr_ammo(ammo); int v11 = v10; if (ammoQuantity < ammoCapacity) { int v12; if (ammoQuantity + v10 > ammoCapacity) { v11 = v10 - (ammoCapacity - ammoQuantity); v12 = ammoCapacity; } else { v11 = 0; v12 = ammoQuantity + v10; } weapon->data.item.weapon.ammoTypePid = ammo->pid; item_w_set_curr_ammo(ammo, v11); item_w_set_curr_ammo(weapon, v12); } return v11; } // 0x478A1C int item_w_range(Object* critter, int hitMode) { int range; int v12; // NOTE: Uninline. Object* weapon = item_hit_with(critter, hitMode); if (weapon != NULL && hitMode != 4 && hitMode != 5 && (hitMode < 8 || hitMode > 19)) { Proto* proto; proto_ptr(weapon->pid, &proto); if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) { range = proto->item.data.weapon.maxRange1; } else { range = proto->item.data.weapon.maxRange2; } if (item_w_subtype(weapon, hitMode) == ATTACK_TYPE_THROW) { if (critter == obj_dude) { v12 = critterGetStat(critter, STAT_STRENGTH) + 2 * perk_level(critter, PERK_HEAVE_HO); } else { v12 = critterGetStat(critter, STAT_STRENGTH); } int maxRange = 3 * v12; if (range >= maxRange) { range = maxRange; } } return range; } if (critter_flag_check(critter->pid, CRITTER_LONG_LIMBS)) { return 2; } return 1; } // Returns action points required for hit mode. // // 0x478B24 int item_w_mp_cost(Object* critter, int hitMode, bool aiming) { int actionPoints; // NOTE: Uninline. Object* weapon = item_hit_with(critter, hitMode); if (hitMode == HIT_MODE_LEFT_WEAPON_RELOAD || hitMode == HIT_MODE_RIGHT_WEAPON_RELOAD) { if (weapon != NULL) { Proto* proto; proto_ptr(weapon->pid, &proto); if (proto->item.data.weapon.perk == PERK_WEAPON_FAST_RELOAD) { return 1; } if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { return 0; } } return 2; } switch (hitMode) { case HIT_MODE_PALM_STRIKE: actionPoints = 6; break; case HIT_MODE_PIERCING_STRIKE: actionPoints = 8; break; case HIT_MODE_STRONG_KICK: case HIT_MODE_SNAP_KICK: case HIT_MODE_POWER_KICK: actionPoints = 4; break; case HIT_MODE_HIP_KICK: case HIT_MODE_HOOK_KICK: actionPoints = 7; break; case HIT_MODE_PIERCING_KICK: actionPoints = 9; break; default: // TODO: Inverse conditions. if (weapon != NULL && hitMode != HIT_MODE_PUNCH && hitMode != HIT_MODE_KICK && hitMode != HIT_MODE_STRONG_PUNCH && hitMode != HIT_MODE_HAMMER_PUNCH && hitMode != HIT_MODE_HAYMAKER) { if (hitMode == HIT_MODE_LEFT_WEAPON_PRIMARY || hitMode == HIT_MODE_RIGHT_WEAPON_PRIMARY) { // NOTE: Uninline. actionPoints = item_w_primary_mp_cost(weapon); } else { // NOTE: Uninline. actionPoints = item_w_secondary_mp_cost(weapon); } if (critter == obj_dude) { if (trait_level(TRAIT_FAST_SHOT)) { if (item_w_range(critter, hitMode) > 2) { actionPoints--; } } } } else { actionPoints = 3; } break; } if (critter == obj_dude) { int attackType = item_w_subtype(weapon, hitMode); if (perkHasRank(obj_dude, PERK_BONUS_HTH_ATTACKS)) { if (attackType == ATTACK_TYPE_MELEE || attackType == ATTACK_TYPE_UNARMED) { actionPoints -= 1; } } if (perkHasRank(obj_dude, PERK_BONUS_RATE_OF_FIRE)) { if (attackType == ATTACK_TYPE_RANGED) { actionPoints -= 1; } } } if (aiming) { actionPoints += 1; } if (actionPoints < 1) { actionPoints = 1; } return actionPoints; } // 0x478D08 int item_w_min_st(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.minStrength; } // 0x478D30 int item_w_crit_fail(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.criticalFailureType; } // 0x478D58 int item_w_perk(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.perk; } // 0x478D80 int item_w_rounds(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.rounds; } // 0x478DA8 int item_w_anim_code(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.animationCode; } // 0x478DD0 int item_w_proj_pid(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.projectilePid; } // 0x478DF8 int item_w_ammo_pid(Object* weapon) { if (weapon == NULL) { return -1; } if (item_get_type(weapon) != ITEM_TYPE_WEAPON) { return -1; } return weapon->data.item.weapon.ammoTypePid; } // 0x478E18 char item_w_sound_id(Object* weapon) { if (weapon == NULL) { return '\0'; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.soundCode & 0xFF; } // 0x478E5C int item_w_called_shot(Object* critter, int hitMode) { if (critter == obj_dude && trait_level(TRAIT_FAST_SHOT)) { return 0; } // NOTE: Uninline. int anim = item_w_anim(critter, hitMode); if (anim == ANIM_FIRE_BURST || anim == ANIM_FIRE_CONTINUOUS) { return 0; } // NOTE: Uninline. Object* weapon = item_hit_with(critter, hitMode); int damageType = item_w_damage_type(critter, weapon); return damageType != DAMAGE_TYPE_EXPLOSION && damageType != DAMAGE_TYPE_FIRE && damageType != DAMAGE_TYPE_EMP && (damageType != DAMAGE_TYPE_PLASMA || anim != ANIM_THROW_ANIM); } // 0x478EF4 int item_w_can_unload(Object* weapon) { if (weapon == NULL) { return false; } if (item_get_type(weapon) != ITEM_TYPE_WEAPON) { return false; } // NOTE: Uninline. int ammoCapacity = item_w_max_ammo(weapon); if (ammoCapacity <= 0) { return false; } // NOTE: Uninline. int ammoQuantity = item_w_curr_ammo(weapon); if (ammoQuantity <= 0) { return false; } if (weapon->pid == PROTO_ID_SOLAR_SCORCHER) { return false; } if (item_w_ammo_pid(weapon) == -1) { return false; } return true; } // 0x478F80 Object* item_w_unload(Object* weapon) { if (!item_w_can_unload(weapon)) { return NULL; } // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(weapon); if (ammoTypePid == -1) { return NULL; } Object* ammo; if (obj_pid_new(&ammo, ammoTypePid) != 0) { return NULL; } obj_disconnect(ammo, NULL); // NOTE: Uninline. int ammoQuantity = item_w_curr_ammo(weapon); // NOTE: Uninline. int ammoCapacity = item_w_max_ammo(ammo); int remainingQuantity; if (ammoQuantity <= ammoCapacity) { item_w_set_curr_ammo(ammo, ammoQuantity); remainingQuantity = 0; } else { item_w_set_curr_ammo(ammo, ammoCapacity); remainingQuantity = ammoQuantity - ammoCapacity; } item_w_set_curr_ammo(weapon, remainingQuantity); return ammo; } // 0x47905C int item_w_primary_mp_cost(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.actionPointCost1; } // NOTE: Inlined. // // 0x479084 int item_w_secondary_mp_cost(Object* weapon) { if (weapon == NULL) { return -1; } Proto* proto; proto_ptr(weapon->pid, &proto); return proto->item.data.weapon.actionPointCost2; } // 0x4790AC int item_w_compute_ammo_cost(Object* obj, int* inout_a2) { int pid; if (inout_a2 == NULL) { return -1; } if (obj == NULL) { return 0; } pid = obj->pid; if (pid == PROTO_ID_SUPER_CATTLE_PROD || pid == PROTO_ID_MEGA_POWER_FIST) { *inout_a2 *= 2; } return 0; } // 0x4790E8 bool item_w_is_grenade(Object* weapon) { int damageType = item_w_damage_type(NULL, weapon); return damageType == DAMAGE_TYPE_EXPLOSION || damageType == DAMAGE_TYPE_PLASMA || damageType == DAMAGE_TYPE_EMP; } // 0x47910C int item_w_area_damage_radius(Object* weapon, int hitMode) { int attackType = item_w_subtype(weapon, hitMode); int anim = item_w_anim_weap(weapon, hitMode); int damageType = item_w_damage_type(NULL, weapon); int damageRadius = 0; if (attackType == ATTACK_TYPE_RANGED) { if (anim == ANIM_FIRE_SINGLE && damageType == DAMAGE_TYPE_EXPLOSION) { // NOTE: Uninline. damageRadius = item_w_rocket_dmg_radius(weapon); } } else if (attackType == ATTACK_TYPE_THROW) { // NOTE: Uninline. if (item_w_is_grenade(weapon)) { // NOTE: Uninline. damageRadius = item_w_grenade_dmg_radius(weapon); } } return damageRadius; } // 0x479180 int item_w_grenade_dmg_radius(Object* weapon) { return 2; } // 0x479188 int item_w_rocket_dmg_radius(Object* weapon) { return 3; } // 0x479190 int item_w_ac_adjust(Object* weapon) { // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(weapon); if (ammoTypePid == -1) { return 0; } Proto* proto; if (proto_ptr(ammoTypePid, &proto) == -1) { return 0; } return proto->item.data.ammo.armorClassModifier; } // 0x4791E0 int item_w_dr_adjust(Object* weapon) { // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(weapon); if (ammoTypePid == -1) { return 0; } Proto* proto; if (proto_ptr(ammoTypePid, &proto) == -1) { return 0; } return proto->item.data.ammo.damageResistanceModifier; } // 0x479230 int item_w_dam_mult(Object* weapon) { // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(weapon); if (ammoTypePid == -1) { return 1; } Proto* proto; if (proto_ptr(ammoTypePid, &proto) == -1) { return 1; } return proto->item.data.ammo.damageMultiplier; } // 0x479294 int item_w_dam_div(Object* weapon) { // NOTE: Uninline. int ammoTypePid = item_w_ammo_pid(weapon); if (ammoTypePid == -1) { return 1; } Proto* proto; if (proto_ptr(ammoTypePid, &proto) == -1) { return 1; } return proto->item.data.ammo.damageDivisor; } // 0x4792F8 int item_ar_ac(Object* armor) { if (armor == NULL) { return 0; } Proto* proto; proto_ptr(armor->pid, &proto); return proto->item.data.armor.armorClass; } // 0x479318 int item_ar_dr(Object* armor, int damageType) { if (armor == NULL) { return 0; } Proto* proto; proto_ptr(armor->pid, &proto); return proto->item.data.armor.damageResistance[damageType]; } // 0x479338 int item_ar_dt(Object* armor, int damageType) { if (armor == NULL) { return 0; } Proto* proto; proto_ptr(armor->pid, &proto); return proto->item.data.armor.damageThreshold[damageType]; } // 0x479358 int item_ar_perk(Object* armor) { if (armor == NULL) { return -1; } Proto* proto; proto_ptr(armor->pid, &proto); return proto->item.data.armor.perk; } // 0x479380 int item_ar_male_fid(Object* armor) { if (armor == NULL) { return -1; } Proto* proto; proto_ptr(armor->pid, &proto); return proto->item.data.armor.maleFid; } // 0x4793A8 int item_ar_female_fid(Object* armor) { if (armor == NULL) { return -1; } Proto* proto; proto_ptr(armor->pid, &proto); return proto->item.data.armor.femaleFid; } // 0x4793D0 int item_m_max_charges(Object* miscItem) { if (miscItem == NULL) { return 0; } Proto* proto; proto_ptr(miscItem->pid, &proto); return proto->item.data.misc.charges; } // 0x4793F0 int item_m_curr_charges(Object* miscItem) { if (miscItem == NULL) { return 0; } return miscItem->data.item.misc.charges; } // 0x4793F8 int item_m_set_charges(Object* miscItem, int charges) { // NOTE: Uninline. int maxCharges = item_m_max_charges(miscItem); if (charges > maxCharges) { charges = maxCharges; } miscItem->data.item.misc.charges = charges; return 0; } // NOTE: Unused. // // 0x479434 int item_m_cell(Object* miscItem) { if (miscItem == NULL) { return 0; } Proto* proto; proto_ptr(miscItem->pid, &proto); return proto->item.data.misc.powerType; } // NOTE: Inlined. // // 0x479454 int item_m_cell_pid(Object* miscItem) { if (miscItem == NULL) { return -1; } Proto* proto; proto_ptr(miscItem->pid, &proto); return proto->item.data.misc.powerTypePid; } // 0x47947C bool item_m_uses_charges(Object* miscItem) { if (miscItem == NULL) { return false; } Proto* proto; proto_ptr(miscItem->pid, &proto); return proto->item.data.misc.charges != 0; } // 0x4794A4 int item_m_use_charged_item(Object* critter, Object* miscItem) { int pid = miscItem->pid; if (pid == PROTO_ID_STEALTH_BOY_I || pid == PROTO_ID_GEIGER_COUNTER_I || pid == PROTO_ID_STEALTH_BOY_II || pid == PROTO_ID_GEIGER_COUNTER_II) { // NOTE: Uninline. bool isOn = item_m_on(miscItem); if (isOn) { item_m_turn_off(miscItem); } else { item_m_turn_on(miscItem); } } else if (pid == PROTO_ID_MOTION_SENSOR) { // NOTE: Uninline. if (item_m_dec_charges(miscItem) == 0) { automap(true, true); } else { MessageListItem messageListItem; // %s has no charges left. messageListItem.num = 5; if (message_search(&item_message_file, &messageListItem)) { char text[80]; const char* itemName = object_name(miscItem); sprintf(text, messageListItem.text, itemName); display_print(text); } } } return 0; } // 0x4795A4 int item_m_dec_charges(Object* item) { // NOTE: Uninline. int charges = item_m_curr_charges(item); if (charges <= 0) { return -1; } // NOTE: Uninline. item_m_set_charges(item, charges - 1); return 0; } // 0x4795F0 int item_m_trickle(Object* item, void* data) { // NOTE: Uninline. if (item_m_dec_charges(item) == 0) { int delay; if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) { delay = 600; } else { delay = 3000; } queue_add(delay, item, NULL, EVENT_TYPE_ITEM_TRICKLE); } else { Object* critter = obj_top_environment(item); if (critter == obj_dude) { MessageListItem messageListItem; // %s has no charges left. messageListItem.num = 5; if (message_search(&item_message_file, &messageListItem)) { char text[80]; const char* itemName = object_name(item); sprintf(text, messageListItem.text, itemName); display_print(text); } } item_m_turn_off(item); } return 0; } // 0x4796A8 bool item_m_on(Object* obj) { if (obj == NULL) { return false; } if (!item_m_uses_charges(obj)) { return false; } return queue_find(obj, EVENT_TYPE_ITEM_TRICKLE); } // Turns on geiger counter or stealth boy. // // 0x4796D0 int item_m_turn_on(Object* item) { MessageListItem messageListItem; char text[80]; Object* critter = obj_top_environment(item); if (critter == NULL) { // This item can only be used from the interface bar. messageListItem.num = 9; if (message_search(&item_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } // NOTE: Uninline. if (item_m_dec_charges(item) != 0) { if (critter == obj_dude) { messageListItem.num = 5; if (message_search(&item_message_file, &messageListItem)) { char* name = object_name(item); sprintf(text, messageListItem.text, name); display_print(text); } } return -1; } if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) { queue_add(600, item, 0, EVENT_TYPE_ITEM_TRICKLE); item->pid = PROTO_ID_STEALTH_BOY_II; if (critter != NULL) { // NOTE: Uninline. item_m_stealth_effect_on(critter); } } else { queue_add(3000, item, 0, EVENT_TYPE_ITEM_TRICKLE); item->pid = PROTO_ID_GEIGER_COUNTER_II; } if (critter == obj_dude) { // %s is on. messageListItem.num = 6; if (message_search(&item_message_file, &messageListItem)) { char* name = object_name(item); sprintf(text, messageListItem.text, name); display_print(text); } if (item->pid == PROTO_ID_GEIGER_COUNTER_II) { // You pass the Geiger counter over you body. The rem counter reads: %d messageListItem.num = 8; if (message_search(&item_message_file, &messageListItem)) { int radiation = critter_get_rads(critter); sprintf(text, messageListItem.text, radiation); display_print(text); } } } return 0; } // Turns off geiger counter or stealth boy. // // 0x479898 int item_m_turn_off(Object* item) { Object* owner = obj_top_environment(item); queue_remove_this(item, EVENT_TYPE_ITEM_TRICKLE); if (owner != NULL && item->pid == PROTO_ID_STEALTH_BOY_II) { item_m_stealth_effect_off(owner, item); } if (item->pid == PROTO_ID_STEALTH_BOY_I || item->pid == PROTO_ID_STEALTH_BOY_II) { item->pid = PROTO_ID_STEALTH_BOY_I; } else { item->pid = PROTO_ID_GEIGER_COUNTER_I; } if (owner == obj_dude) { intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } if (owner == obj_dude) { // %s is off. MessageListItem messageListItem; messageListItem.num = 7; if (message_search(&item_message_file, &messageListItem)) { const char* name = object_name(item); char text[80]; sprintf(text, messageListItem.text, name); display_print(text); } } return 0; } // 0x479954 int item_m_turn_off_from_queue(Object* obj, void* data) { item_m_turn_off(obj); return 1; } // NOTE: Inlined. // // 0x479960 static int item_m_stealth_effect_on(Object* object) { if ((object->flags & OBJECT_TRANS_GLASS) != 0) { return -1; } object->flags |= OBJECT_TRANS_GLASS; Rect rect; obj_bound(object, &rect); tile_refresh_rect(&rect, object->elevation); return 0; } // 0x479998 static int item_m_stealth_effect_off(Object* critter, Object* item) { Object* item1 = inven_left_hand(critter); if (item1 != NULL && item1 != item && item1->pid == PROTO_ID_STEALTH_BOY_II) { return -1; } Object* item2 = inven_right_hand(critter); if (item2 != NULL && item2 != item && item2->pid == PROTO_ID_STEALTH_BOY_II) { return -1; } if ((critter->flags & OBJECT_TRANS_GLASS) == 0) { return -1; } critter->flags &= ~OBJECT_TRANS_GLASS; Rect rect; obj_bound(critter, &rect); tile_refresh_rect(&rect, critter->elevation); return 0; } // 0x479A00 int item_c_max_size(Object* container) { if (container == NULL) { return 0; } Proto* proto; proto_ptr(container->pid, &proto); return proto->item.data.container.maxSize; } // 0x479A20 int item_c_curr_size(Object* container) { if (container == NULL) { return 0; } int totalSize = 0; Inventory* inventory = &(container->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); int size = item_size(inventoryItem->item); totalSize += inventory->items[index].quantity * size; } return totalSize; } // 0x479A74 int item_a_ac_adjust(Object* armor) { if (armor == NULL) { return 0; } Proto* proto; if (proto_ptr(armor->pid, &proto) == -1) { return 0; } return proto->item.data.ammo.armorClassModifier; } // 0x479AA4 int item_a_dr_adjust(Object* armor) { if (armor == NULL) { return 0; } Proto* proto; if (proto_ptr(armor->pid, &proto) == -1) { return 0; } return proto->item.data.ammo.damageResistanceModifier; } // 0x479AD4 int item_a_dam_mult(Object* armor) { if (armor == NULL) { return 0; } Proto* proto; if (proto_ptr(armor->pid, &proto) == -1) { return 0; } return proto->item.data.ammo.damageMultiplier; } // 0x479B04 int item_a_dam_div(Object* armor) { if (armor == NULL) { return 0; } Proto* proto; if (proto_ptr(armor->pid, &proto) == -1) { return 0; } return proto->item.data.ammo.damageDivisor; } // 0x479B44 static int insert_drug_effect(Object* critter, Object* item, int a3, int* stats, int* mods) { int index; for (index = 0; index < 3; index++) { if (mods[index] != 0) { break; } } if (index == 3) { return -1; } DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)mem_malloc(sizeof(*drugEffectEvent)); if (drugEffectEvent == NULL) { return -1; } drugEffectEvent->drugPid = item->pid; for (index = 0; index < 3; index++) { drugEffectEvent->stats[index] = stats[index]; drugEffectEvent->modifiers[index] = mods[index]; } int delay = 600 * a3; if (critter == obj_dude) { if (trait_level(TRAIT_CHEM_RESISTANT)) { delay /= 2; } } if (queue_add(delay, critter, drugEffectEvent, EVENT_TYPE_DRUG) == -1) { mem_free(drugEffectEvent); return -1; } return 0; } // 0x479C20 static void perform_drug_effect(Object* critter, int* stats, int* mods, bool isImmediate) { int v10; int v11; int v12; MessageListItem messageListItem; const char* name; const char* text; char v24[92]; // TODO: Size is probably wrong. char str[92]; // TODO: Size is probably wrong. bool statsChanged = false; int v5 = 0; bool v32 = false; if (stats[0] == -2) { v5 = 1; v32 = true; } for (int index = v5; index < 3; index++) { int stat = stats[index]; if (stat == -1) { continue; } if (stat == STAT_CURRENT_HIT_POINTS) { critter->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; } v10 = stat_get_bonus(critter, stat); int before; if (critter == obj_dude) { before = critterGetStat(obj_dude, stat); } if (v32) { v11 = roll_random(mods[index - 1], mods[index]) + v10; v32 = false; } else { v11 = mods[index] + v10; } if (stat == STAT_CURRENT_HIT_POINTS) { v12 = stat_get_base(critter, STAT_CURRENT_HIT_POINTS); if (v11 + v12 <= 0 && critter != obj_dude) { name = critter_name(critter); // %s succumbs to the adverse effects of chems. text = getmsg(&item_message_file, &messageListItem, 600); sprintf(v24, text, name); combatKillCritterOutsideCombat(critter, v24); } } stat_set_bonus(critter, stat, v11); if (critter == obj_dude) { if (stat == STAT_CURRENT_HIT_POINTS) { intface_update_hit_points(true); } int after = critterGetStat(critter, stat); if (after != before) { // 1 - You gained %d %s. // 2 - You lost %d %s. messageListItem.num = after < before ? 2 : 1; if (message_search(&item_message_file, &messageListItem)) { char* statName = stat_name(stat); sprintf(str, messageListItem.text, after < before ? before - after : after - before, statName); display_print(str); statsChanged = true; } } } } if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) > 0) { if (critter == obj_dude && !statsChanged && isImmediate) { // Nothing happens. messageListItem.num = 10; if (message_search(&item_message_file, &messageListItem)) { display_print(messageListItem.text); } } } else { if (critter == obj_dude) { // You suffer a fatal heart attack from chem overdose. messageListItem.num = 4; if (message_search(&item_message_file, &messageListItem)) { strcpy(v24, messageListItem.text); // TODO: Why message is ignored? } } else { name = critter_name(critter); // %s succumbs to the adverse effects of chems. text = getmsg(&item_message_file, &messageListItem, 600); sprintf(v24, text, name); // TODO: Why message is ignored? } } } // 0x479EE4 static bool drug_effect_allowed(Object* critter, int pid) { int index; DrugDescription* drugDescription; for (index = 0; index < ADDICTION_COUNT; index++) { drugDescription = &(drugInfoList[index]); if (drugDescription->drugPid == pid) { break; } } if (index == ADDICTION_COUNT) { return true; } if (drugDescription->field_8 == 0) { return true; } // TODO: Probably right, but let's check it once. int count = 0; DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)queue_find_first(critter, EVENT_TYPE_DRUG); while (drugEffectEvent != NULL) { if (drugEffectEvent->drugPid == pid) { count++; if (count >= drugDescription->field_8) { return false; } } drugEffectEvent = (DrugEffectEvent*)queue_find_next(critter, EVENT_TYPE_DRUG); } return true; } // 0x479F60 int item_d_take_drug(Object* critter, Object* item) { if (critter_is_dead(critter)) { return -1; } if (critter_body_type(critter) == BODY_TYPE_ROBOTIC) { return -1; } Proto* proto; proto_ptr(item->pid, &proto); if (item->pid == PROTO_ID_JET_ANTIDOTE) { if (item_d_check_addict(PROTO_ID_JET)) { perform_withdrawal_end(critter, PERK_JET_ADDICTION); if (critter == obj_dude) { // NOTE: Uninline. item_d_unset_addict(PROTO_ID_JET); } return 0; } } wd_obj = critter; wd_gvar = pid_to_gvar(item->pid); wd_onset = proto->item.data.drug.withdrawalOnset; queue_clear_type(EVENT_TYPE_WITHDRAWAL, item_wd_clear_all); if (drug_effect_allowed(critter, item->pid)) { perform_drug_effect(critter, proto->item.data.drug.stat, proto->item.data.drug.amount, true); insert_drug_effect(critter, item, proto->item.data.drug.duration1, proto->item.data.drug.stat, proto->item.data.drug.amount1); insert_drug_effect(critter, item, proto->item.data.drug.duration2, proto->item.data.drug.stat, proto->item.data.drug.amount2); } else { if (critter == obj_dude) { MessageListItem messageListItem; // That didn't seem to do that much. char* msg = getmsg(&item_message_file, &messageListItem, 50); display_print(msg); } } if (!item_d_check_addict(item->pid)) { int addictionChance = proto->item.data.drug.addictionChance; if (critter == obj_dude) { if (trait_level(TRAIT_CHEM_RELIANT)) { addictionChance *= 2; } if (trait_level(TRAIT_CHEM_RESISTANT)) { addictionChance /= 2; } if (perk_level(obj_dude, PERK_FLOWER_CHILD)) { addictionChance /= 2; } } if (roll_random(1, 100) <= addictionChance) { insert_withdrawal(critter, 1, proto->item.data.drug.withdrawalOnset, proto->item.data.drug.withdrawalEffect, item->pid); if (critter == obj_dude) { // NOTE: Uninline. item_d_set_addict(item->pid); } } } return 1; } // 0x47A178 int item_d_clear(Object* obj, void* data) { if (isPartyMember(obj)) { return 0; } item_d_process(obj, data); return 1; } // 0x47A198 int item_d_process(Object* obj, void* data) { DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)data; if (obj == NULL) { return 0; } if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { return 0; } perform_drug_effect(obj, drugEffectEvent->stats, drugEffectEvent->modifiers, false); if (!(obj->data.critter.combat.results & DAM_DEAD)) { return 0; } return 1; } // 0x47A1D0 int item_d_load(File* stream, void** dataPtr) { DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)mem_malloc(sizeof(*drugEffectEvent)); if (drugEffectEvent == NULL) { return -1; } if (db_freadIntCount(stream, drugEffectEvent->stats, 3) == -1) goto err; if (db_freadIntCount(stream, drugEffectEvent->modifiers, 3) == -1) goto err; *dataPtr = drugEffectEvent; return 0; err: mem_free(drugEffectEvent); return -1; } // 0x47A254 int item_d_save(File* stream, void* data) { DrugEffectEvent* drugEffectEvent = (DrugEffectEvent*)data; if (db_fwriteIntCount(stream, drugEffectEvent->stats, 3) == -1) return -1; if (db_fwriteIntCount(stream, drugEffectEvent->modifiers, 3) == -1) return -1; return 0; } // 0x47A290 static int insert_withdrawal(Object* obj, int a2, int duration, int perk, int pid) { WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)mem_malloc(sizeof(*withdrawalEvent)); if (withdrawalEvent == NULL) { return -1; } withdrawalEvent->field_0 = a2; withdrawalEvent->pid = pid; withdrawalEvent->perk = perk; if (queue_add(600 * duration, obj, withdrawalEvent, EVENT_TYPE_WITHDRAWAL) == -1) { mem_free(withdrawalEvent); return -1; } return 0; } // 0x47A2FC int item_wd_clear(Object* obj, void* data) { WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data; if (isPartyMember(obj)) { return 0; } if (!withdrawalEvent->field_0) { perform_withdrawal_end(obj, withdrawalEvent->perk); } return 1; } // 0x47A324 static int item_wd_clear_all(Object* a1, void* data) { WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data; if (a1 != wd_obj) { return 0; } if (pid_to_gvar(withdrawalEvent->pid) != wd_gvar) { return 0; } if (!withdrawalEvent->field_0) { perform_withdrawal_end(wd_obj, withdrawalEvent->perk); } insert_withdrawal(a1, 1, wd_onset, withdrawalEvent->perk, withdrawalEvent->pid); wd_obj = NULL; return 1; } // 0x47A384 int item_wd_process(Object* obj, void* data) { WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data; if (withdrawalEvent->field_0) { perform_withdrawal_start(obj, withdrawalEvent->perk, withdrawalEvent->pid); } else { if (withdrawalEvent->perk == PERK_JET_ADDICTION) { return 0; } perform_withdrawal_end(obj, withdrawalEvent->perk); if (obj == obj_dude) { // NOTE: Uninline. item_d_unset_addict(withdrawalEvent->pid); } } if (obj == obj_dude) { return 1; } return 0; } // 0x47A404 int item_wd_load(File* stream, void** dataPtr) { WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)mem_malloc(sizeof(*withdrawalEvent)); if (withdrawalEvent == NULL) { return -1; } if (db_freadInt(stream, &(withdrawalEvent->field_0)) == -1) goto err; if (db_freadInt(stream, &(withdrawalEvent->pid)) == -1) goto err; if (db_freadInt(stream, &(withdrawalEvent->perk)) == -1) goto err; *dataPtr = withdrawalEvent; return 0; err: mem_free(withdrawalEvent); return -1; } // 0x47A484 int item_wd_save(File* stream, void* data) { WithdrawalEvent* withdrawalEvent = (WithdrawalEvent*)data; if (db_fwriteInt(stream, withdrawalEvent->field_0) == -1) return -1; if (db_fwriteInt(stream, withdrawalEvent->pid) == -1) return -1; if (db_fwriteInt(stream, withdrawalEvent->perk) == -1) return -1; return 0; } // 0x47A4C4 static void perform_withdrawal_start(Object* obj, int perk, int pid) { if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { debug_printf("\nERROR: perform_withdrawal_start: Was called on non-critter!"); return; } perk_add_effect(obj, perk); if (obj == obj_dude) { char* description = perk_description(perk); display_print(description); } int duration = 10080; if (obj == obj_dude) { if (trait_level(TRAIT_CHEM_RELIANT)) { duration /= 2; } if (perk_level(obj, PERK_FLOWER_CHILD)) { duration /= 2; } } insert_withdrawal(obj, 0, duration, perk, pid); } // 0x47A558 static void perform_withdrawal_end(Object* obj, int perk) { if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { debug_printf("\nERROR: perform_withdrawal_end: Was called on non-critter!"); return; } perk_remove_effect(obj, perk); if (obj == obj_dude) { MessageListItem messageListItem; messageListItem.num = 3; if (message_search(&item_message_file, &messageListItem)) { display_print(messageListItem.text); } } } // 0x47A5B4 static int pid_to_gvar(int drugPid) { for (int index = 0; index < ADDICTION_COUNT; index++) { DrugDescription* drugDescription = &(drugInfoList[index]); if (drugDescription->drugPid == drugPid) { return drugDescription->gvar; } } return -1; } // NOTE: Inlined. // // 0x47A5E8 void item_d_set_addict(int drugPid) { int gvar = pid_to_gvar(drugPid); if (gvar != -1) { game_global_vars[gvar] = 1; } pc_flag_on(DUDE_STATE_ADDICTED); } // NOTE: Inlined. // // 0x47A60C void item_d_unset_addict(int drugPid) { int gvar = pid_to_gvar(drugPid); if (gvar != -1) { game_global_vars[gvar] = 0; } if (!item_d_check_addict(-1)) { pc_flag_off(DUDE_STATE_ADDICTED); } } // Returns `true` if dude has addiction to item with given pid or any addition // if [pid] is -1. // // 0x47A640 bool item_d_check_addict(int drugPid) { for (int index = 0; index < ADDICTION_COUNT; index++) { DrugDescription* drugDescription = &(drugInfoList[index]); if (drugPid == -1 || drugPid == drugDescription->drugPid) { if (game_global_vars[drugDescription->gvar] != 0) { return true; } else { return false; } } } return false; } // item_caps_total // 0x47A6A8 int item_caps_total(Object* obj) { int amount = 0; Inventory* inventory = &(obj->data.inventory); for (int i = 0; i < inventory->length; i++) { InventoryItem* inventoryItem = &(inventory->items[i]); Object* item = inventoryItem->item; if (item->pid == PROTO_ID_MONEY) { amount += inventoryItem->quantity; } else { if (item_get_type(item) == ITEM_TYPE_CONTAINER) { // recursively collect amount of caps in container amount += item_caps_total(item); } } } return amount; } // item_caps_adjust // 0x47A6F8 int item_caps_adjust(Object* obj, int amount) { int caps = item_caps_total(obj); if (amount < 0 && caps < -amount) { return -1; } if (amount <= 0 || caps != 0) { Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length && amount != 0; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item->pid == PROTO_ID_MONEY) { if (amount <= 0 && -amount >= inventoryItem->quantity) { obj_erase_object(item, NULL); amount += inventoryItem->quantity; // NOTE: Uninline. item_compact(index, inventory); index = -1; } else { inventoryItem->quantity += amount; amount = 0; } } } for (int index = 0; index < inventory->length && amount != 0; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item_get_type(item) == ITEM_TYPE_CONTAINER) { int capsInContainer = item_caps_total(item); if (amount <= 0 || capsInContainer <= 0) { if (amount < 0) { if (capsInContainer < -amount) { if (item_caps_adjust(item, capsInContainer) == 0) { amount += capsInContainer; } } else { if (item_caps_adjust(item, amount) == 0) { amount = 0; } } } } else { if (item_caps_adjust(item, amount) == 0) { amount = 0; } } } } return 0; } Object* item; if (obj_pid_new(&item, PROTO_ID_MONEY) == 0) { obj_disconnect(item, NULL); if (item_add_force(obj, item, amount) != 0) { obj_erase_object(item, NULL); return -1; } } return 0; } // 0x47A8C8 int item_caps_get_amount(Object* item) { if (item->pid != PROTO_ID_MONEY) { return -1; } return item->data.item.misc.charges; } // 0x47A8D8 int item_caps_set_amount(Object* item, int amount) { if (item->pid != PROTO_ID_MONEY) { return -1; } item->data.item.misc.charges = amount; return 0; } ================================================ FILE: src/game/item.h ================================================ #ifndef FALLOUT_GAME_ITEM_H_ #define FALLOUT_GAME_ITEM_H_ #include #include "plib/db/db.h" #include "game/object_types.h" #define ADDICTION_COUNT 9 typedef enum AttackType { ATTACK_TYPE_NONE, ATTACK_TYPE_UNARMED, ATTACK_TYPE_MELEE, ATTACK_TYPE_THROW, ATTACK_TYPE_RANGED, ATTACK_TYPE_COUNT, } AttackType; typedef struct DrugDescription { int drugPid; int gvar; int field_8; } DrugDescription; extern DrugDescription drugInfoList[ADDICTION_COUNT]; int item_init(); void item_reset(); void item_exit(); int item_load(File* stream); int item_save(File* stream); int item_add_mult(Object* owner, Object* itemToAdd, int quantity); int item_add_force(Object* owner, Object* itemToAdd, int quantity); int item_remove_mult(Object* a1, Object* a2, int quantity); int item_move(Object* a1, Object* a2, Object* a3, int quantity); int item_move_force(Object* a1, Object* a2, Object* a3, int quantity); void item_move_all(Object* a1, Object* a2); int item_move_all_hidden(Object* a1, Object* a2); int item_destroy_all_hidden(Object* a1); int item_drop_all(Object* critter, int tile); char* item_name(Object* obj); char* item_description(Object* obj); int item_get_type(Object* item); int item_material(Object* item); int item_size(Object* obj); int item_weight(Object* item); int item_cost(Object* obj); int item_total_cost(Object* obj); int item_total_weight(Object* obj); bool item_grey(Object* item_obj); int item_inv_fid(Object* obj); Object* item_hit_with(Object* critter, int hitMode); int item_mp_cost(Object* obj, int hitMode, bool aiming); int item_count(Object* obj, Object* a2); int item_queued(Object* obj); Object* item_replace(Object* a1, Object* a2, int a3); int item_is_hidden(Object* obj); int item_w_subtype(Object* a1, int a2); int item_w_skill(Object* a1, int a2); int item_w_skill_level(Object* a1, int a2); int item_w_damage_min_max(Object* weapon, int* minDamagePtr, int* maxDamagePtr); int item_w_damage(Object* critter, int hitMode); int item_w_damage_type(Object* critter, Object* weapon); int item_w_is_2handed(Object* weapon); int item_w_anim(Object* critter, int hitMode); int item_w_anim_weap(Object* weapon, int hitMode); int item_w_max_ammo(Object* ammoOrWeapon); int item_w_curr_ammo(Object* ammoOrWeapon); int item_w_caliber(Object* ammoOrWeapon); void item_w_set_curr_ammo(Object* ammoOrWeapon, int quantity); int item_w_try_reload(Object* critter, Object* weapon); bool item_w_can_reload(Object* weapon, Object* ammo); int item_w_reload(Object* weapon, Object* ammo); int item_w_range(Object* critter, int hitMode); int item_w_mp_cost(Object* critter, int hitMode, bool aiming); int item_w_min_st(Object* weapon); int item_w_crit_fail(Object* weapon); int item_w_perk(Object* weapon); int item_w_rounds(Object* weapon); int item_w_anim_code(Object* weapon); int item_w_proj_pid(Object* weapon); int item_w_ammo_pid(Object* weapon); char item_w_sound_id(Object* weapon); int item_w_called_shot(Object* critter, int hitMode); int item_w_can_unload(Object* weapon); Object* item_w_unload(Object* weapon); int item_w_primary_mp_cost(Object* weapon); int item_w_secondary_mp_cost(Object* weapon); int item_w_compute_ammo_cost(Object* obj, int* inout_a2); bool item_w_is_grenade(Object* weapon); int item_w_area_damage_radius(Object* weapon, int hitMode); int item_w_grenade_dmg_radius(Object* weapon); int item_w_rocket_dmg_radius(Object* weapon); int item_w_ac_adjust(Object* weapon); int item_w_dr_adjust(Object* weapon); int item_w_dam_mult(Object* weapon); int item_w_dam_div(Object* weapon); int item_ar_ac(Object* armor); int item_ar_dr(Object* armor, int damageType); int item_ar_dt(Object* armor, int damageType); int item_ar_perk(Object* armor); int item_ar_male_fid(Object* armor); int item_ar_female_fid(Object* armor); int item_m_max_charges(Object* miscItem); int item_m_curr_charges(Object* miscItem); int item_m_set_charges(Object* miscItem, int charges); int item_m_cell(Object* miscItem); int item_m_cell_pid(Object* miscItem); bool item_m_uses_charges(Object* obj); int item_m_use_charged_item(Object* critter, Object* item); int item_m_dec_charges(Object* miscItem); int item_m_trickle(Object* item_obj, void* data); bool item_m_on(Object* obj); int item_m_turn_on(Object* item_obj); int item_m_turn_off(Object* item_obj); int item_m_turn_off_from_queue(Object* obj, void* data); int item_c_max_size(Object* container); int item_c_curr_size(Object* container); int item_a_ac_adjust(Object* armor); int item_a_dr_adjust(Object* armor); int item_a_dam_mult(Object* armor); int item_a_dam_div(Object* armor); int item_d_take_drug(Object* critter_obj, Object* item_obj); int item_d_clear(Object* obj, void* data); int item_d_process(Object* obj, void* data); int item_d_load(File* stream, void** dataPtr); int item_d_save(File* stream, void* data); int item_wd_clear(Object* obj, void* a2); int item_wd_process(Object* obj, void* data); int item_wd_load(File* stream, void** dataPtr); int item_wd_save(File* stream, void* data); void item_d_set_addict(int drugPid); void item_d_unset_addict(int drugPid); bool item_d_check_addict(int drugPid); int item_caps_total(Object* obj); int item_caps_adjust(Object* obj, int amount); int item_caps_get_amount(Object* obj); int item_caps_set_amount(Object* obj, int a2); #endif /* FALLOUT_GAME_ITEM_H_ */ ================================================ FILE: src/game/light.c ================================================ #include "game/light.h" #include #include "game/map_defs.h" #include "game/object.h" #include "game/perk.h" #include "game/tile.h" // 0x51923C static int ambient_light = LIGHT_LEVEL_MAX; // 0x59E994 static int tile_intensity[ELEVATION_COUNT][HEX_GRID_SIZE]; // 0x47A8F0 int light_init() { light_reset_tiles(); return 0; } // NOTE: From OS X. void light_reset() { light_reset_tiles(); } // NOTE: From OS X. void light_exit() { light_reset_tiles(); } // 0x47A8F8 int light_get_ambient() { return ambient_light; } // 0x47A908 void light_set_ambient(int lightLevel, bool shouldUpdateScreen) { int normalizedLightLevel; int oldLightLevel; normalizedLightLevel = lightLevel + perk_level(obj_dude, PERK_NIGHT_VISION) * LIGHT_LEVEL_NIGHT_VISION_BONUS; if (normalizedLightLevel < LIGHT_LEVEL_MIN) { normalizedLightLevel = LIGHT_LEVEL_MIN; } if (normalizedLightLevel > LIGHT_LEVEL_MAX) { normalizedLightLevel = LIGHT_LEVEL_MAX; } oldLightLevel = ambient_light; ambient_light = normalizedLightLevel; if (shouldUpdateScreen) { if (oldLightLevel != normalizedLightLevel) { tile_refresh_display(); } } } // NOTE: From OS X. void light_increase_ambient(int value, bool shouldUpdateScreen) { light_set_ambient(ambient_light + value, shouldUpdateScreen); } // 0x47A96C void light_decrease_ambient(int value, bool shouldUpdateScreen) { light_set_ambient(ambient_light - value, shouldUpdateScreen); } // 0x47A980 int light_get_tile(int elevation, int tile) { int result; if (!elevationIsValid(elevation)) { return 0; } if (!hexGridTileIsValid(tile)) { return 0; } result = tile_intensity[elevation][tile]; if (result >= LIGHT_LEVEL_MAX) { result = LIGHT_LEVEL_MAX; } return result; } // 0x47A9C4 int light_get_tile_true(int elevation, int tile) { if (!elevationIsValid(elevation)) { return 0; } if (!hexGridTileIsValid(tile)) { return 0; } return tile_intensity[elevation][tile]; } // 0x47A9EC void light_set_tile(int elevation, int tile, int lightIntensity) { if (!elevationIsValid(elevation)) { return; } if (!hexGridTileIsValid(tile)) { return; } tile_intensity[elevation][tile] = lightIntensity; } // 0x47AA10 void light_add_to_tile(int elevation, int tile, int lightIntensity) { if (!elevationIsValid(elevation)) { return; } if (!hexGridTileIsValid(tile)) { return; } tile_intensity[elevation][tile] += lightIntensity; } // 0x47AA48 void light_subtract_from_tile(int elevation, int tile, int lightIntensity) { if (!elevationIsValid(elevation)) { return; } if (!hexGridTileIsValid(tile)) { return; } tile_intensity[elevation][tile] -= lightIntensity; } // 0x47AA84 void light_reset_tiles() { int elevation; int tile; for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) { for (tile = 0; tile < HEX_GRID_SIZE; tile++) { tile_intensity[elevation][tile] = 655; } } } ================================================ FILE: src/game/light.h ================================================ #ifndef FALLOUT_GAME_LIGHT_H_ #define FALLOUT_GAME_LIGHT_H_ #include #define LIGHT_LEVEL_MAX 65536 #define LIGHT_LEVEL_MIN (LIGHT_LEVEL_MAX / 4) // 20% of max light per "Night Vision" rank #define LIGHT_LEVEL_NIGHT_VISION_BONUS (LIGHT_LEVEL_MAX / 5) typedef void(AdjustLightIntensityProc)(int elevation, int tile, int intensity); int light_init(); void light_reset(); void light_exit(); int light_get_ambient(); void light_set_ambient(int lightLevel, bool shouldUpdateScreen); void light_increase_ambient(int value, bool shouldUpdateScreen); void light_decrease_ambient(int value, bool shouldUpdateScreen); int light_get_tile(int elevation, int tile); int light_get_tile_true(int elevation, int tile); void light_set_tile(int elevation, int tile, int intensity); void light_add_to_tile(int elevation, int tile, int intensity); void light_subtract_from_tile(int elevation, int tile, int intensity); void light_reset_tiles(); #endif /* FALLOUT_GAME_LIGHT_H_ */ ================================================ FILE: src/game/lip_sync.c ================================================ #include "game/lip_sync.h" #include #include #include "int/audio.h" #include "plib/gnw/input.h" #include "plib/gnw/debug.h" #include "game/gsound.h" #include "plib/gnw/memory.h" #include "int/sound.h" static char* lips_fix_string(const char* fileName, size_t length); static int lips_stop_speech(); static int lips_read_phoneme_type(unsigned char* phoneme_type, File* stream); static int lips_read_marker_type(SpeechMarker* marker_type, File* stream); static int lips_read_lipsynch_info(LipsData* a1, File* stream); static int lips_make_speech(); // 0x519240 unsigned char head_phoneme_current = 0; // 0x519241 unsigned char head_phoneme_drawn = 0; // 0x519244 int head_marker_current = 0; // 0x519248 bool lips_draw_head = true; // 0x51924C LipsData lip_info = { 2, 22528, 0, NULL, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 100, 0, 0, 0, "TEST", "VOC", "TXT", "LIP", }; // 0x5193B4 int speechStartTime = 0; // 0x613CA0 static char lips_subdir_name[14]; // 0x47AAC0 static char* lips_fix_string(const char* fileName, size_t length) { // 0x613CAE static char tmp_str[50]; strncpy(tmp_str, fileName, length); return tmp_str; } // 0x47AAD8 void lips_bkg_proc() { int v0; SpeechMarker* speech_marker; int v5; v0 = head_marker_current; if ((lip_info.flags & LIPS_FLAG_0x02) != 0) { int v1 = soundGetPosition(lip_info.sound); speech_marker = &(lip_info.markers[v0]); while (v1 > speech_marker->position) { head_phoneme_current = lip_info.phonemes[v0]; v0++; if (v0 >= lip_info.marker_count) { v0 = 0; head_phoneme_current = lip_info.phonemes[0]; if ((lip_info.flags & LIPS_FLAG_0x01) == 0) { // NOTE: Uninline. lips_stop_speech(); v0 = head_marker_current; } break; } speech_marker = &(lip_info.markers[v0]); } if (v0 >= lip_info.marker_count - 1) { head_marker_current = v0; v5 = 0; if (lip_info.marker_count <= 5) { debug_printf("Error: Too few markers to stop speech!"); } else { v5 = 3; } speech_marker = &(lip_info.markers[v5]); if (v1 < speech_marker->position) { v0 = 0; head_phoneme_current = lip_info.phonemes[0]; if ((lip_info.flags & LIPS_FLAG_0x01) == 0) { // NOTE: Uninline. lips_stop_speech(); v0 = head_marker_current; } } } } if (head_phoneme_drawn != head_phoneme_current) { head_phoneme_drawn = head_phoneme_current; lips_draw_head = true; } head_marker_current = v0; soundContinueAll(); } // 0x47AC2C int lips_play_speech() { lip_info.flags |= LIPS_FLAG_0x02; head_marker_current = 0; if (soundSetPosition(lip_info.sound, lip_info.field_20) != 0) { debug_printf("Failed set of start_offset!\n"); } int v2 = head_marker_current; while (1) { head_marker_current = v2; SpeechMarker* speechEntry = &(lip_info.markers[v2]); if (lip_info.field_20 <= speechEntry->position) { break; } v2++; head_phoneme_current = lip_info.phonemes[v2]; } int speechVolume = gsound_speech_volume_get(); soundVolume(lip_info.sound, (int)(speechVolume * 0.69)); speechStartTime = get_time(); if (soundPlay(lip_info.sound) != 0) { debug_printf("Failed play!\n"); // NOTE: Uninline. lips_stop_speech(); } return 0; } // NOTE: Inlined. // // 0x47AD2C static int lips_stop_speech() { head_marker_current = 0; soundStop(lip_info.sound); lip_info.flags &= ~(LIPS_FLAG_0x01 | LIPS_FLAG_0x02); return 0; } // NOTE: Inlined. // // 0x47AD4C static int lips_read_phoneme_type(unsigned char* phoneme_type, File* stream) { return db_freadByte(stream, phoneme_type); } // NOTE: Inlined. // // 0x47AD5C static int lips_read_marker_type(SpeechMarker* marker_type, File* stream) { int marker; // Marker is read into temporary variable. if (db_freadInt(stream, &marker) == -1) return -1; // Position is read directly into struct. if (db_freadLong(stream, &(marker_type->position)) == -1) return -1; marker_type->marker = marker; return 0; } // 0x47AD98 static int lips_read_lipsynch_info(LipsData* lipsData, File* stream) { int sound; int field_14; int phonemes; int markers; if (db_freadLong(stream, &(lipsData->version)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_4)) == -1) return -1; if (db_freadLong(stream, &(lipsData->flags)) == -1) return -1; if (db_freadInt(stream, &(sound)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_10)) == -1) return -1; if (db_freadInt(stream, &(field_14)) == -1) return -1; if (db_freadInt(stream, &(phonemes)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_1C)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_20)) == -1) return -1; if (db_freadLong(stream, &(lipsData->phoneme_count)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_28)) == -1) return -1; if (db_freadLong(stream, &(lipsData->marker_count)) == -1) return -1; if (db_freadInt(stream, &(markers)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_34)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_38)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_3C)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_40)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_44)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_48)) == -1) return -1; if (db_freadLong(stream, &(lipsData->field_4C)) == -1) return -1; if (db_freadByteCount(stream, lipsData->field_50, 8) == -1) return -1; if (db_freadByteCount(stream, lipsData->field_58, 4) == -1) return -1; if (db_freadByteCount(stream, lipsData->field_5C, 4) == -1) return -1; if (db_freadByteCount(stream, lipsData->field_60, 4) == -1) return -1; if (db_freadByteCount(stream, lipsData->field_64, 260) == -1) return -1; // TODO: What for? lipsData->sound = (Sound*)sound; lipsData->field_14 = (void*)field_14; lipsData->phonemes = (unsigned char*)phonemes; lipsData->markers = (SpeechMarker*)markers; return 0; } // lips_load_file // 0x47AFAC int lips_load_file(const char* audioFileName, const char* headFileName) { char* sep; int i; char v60[16]; SpeechMarker* speech_marker; SpeechMarker* prev_speech_marker; char path[260]; strcpy(path, "SOUND\\SPEECH\\"); strcpy(lips_subdir_name, headFileName); strcat(path, lips_subdir_name); strcat(path, "\\"); sep = strchr(path, '.'); if (sep != NULL) { *sep = '\0'; } strcpy(v60, audioFileName); sep = strchr(v60, '.'); if (sep != NULL) { *sep = '\0'; } strcpy(lip_info.field_50, v60); strcat(path, lips_fix_string(lip_info.field_50, sizeof(lip_info.field_50))); strcat(path, "."); strcat(path, lip_info.field_60); lips_free_speech(); // FIXME: stream is not closed if any error is encountered during reading. File* stream = db_fopen(path, "rb"); if (stream != NULL) { if (db_freadLong(stream, &(lip_info.version)) == -1) { return -1; } if (lip_info.version == 1) { debug_printf("\nLoading old save-file version (1)"); if (db_fseek(stream, 0, SEEK_SET) != 0) { return -1; } if (lips_read_lipsynch_info(&lip_info, stream) != 0) { return -1; } } else if (lip_info.version == 2) { debug_printf("\nLoading current save-file version (2)"); if (db_freadLong(stream, &(lip_info.field_4)) == -1) return -1; if (db_freadLong(stream, &(lip_info.flags)) == -1) return -1; if (db_freadLong(stream, &(lip_info.field_10)) == -1) return -1; if (db_freadLong(stream, &(lip_info.field_1C)) == -1) return -1; if (db_freadLong(stream, &(lip_info.phoneme_count)) == -1) return -1; if (db_freadLong(stream, &(lip_info.field_28)) == -1) return -1; if (db_freadLong(stream, &(lip_info.marker_count)) == -1) return -1; if (db_freadByteCount(stream, lip_info.field_50, 8) == -1) return -1; if (db_freadByteCount(stream, lip_info.field_58, 4) == -1) return -1; } else { debug_printf("\nError: Lips file WRONG version: %s!", path); } } lip_info.phonemes = (unsigned char*)mem_malloc(lip_info.phoneme_count); if (lip_info.phonemes == NULL) { debug_printf("Out of memory in lips_load_file.'\n"); return -1; } if (stream != NULL) { for (i = 0; i < lip_info.phoneme_count; i++) { if (lips_read_phoneme_type(&(lip_info.phonemes[i]), stream) != 0) { debug_printf("lips_load_file: Error reading phoneme type.\n"); return -1; } } for (i = 0; i < lip_info.phoneme_count; i++) { unsigned char phoneme = lip_info.phonemes[i]; if (phoneme >= PHONEME_COUNT) { debug_printf("\nLoad error: Speech phoneme %d is invalid (%d)!", i, phoneme); } } } lip_info.markers = (SpeechMarker*)mem_malloc(sizeof(*speech_marker) * lip_info.marker_count); if (lip_info.markers == NULL) { debug_printf("Out of memory in lips_load_file.'\n"); return -1; } if (stream != NULL) { for (i = 0; i < lip_info.marker_count; i++) { // NOTE: Uninline. if (lips_read_marker_type(&(lip_info.markers[i]), stream) != 0) { debug_printf("lips_load_file: Error reading marker type."); return -1; } } speech_marker = &(lip_info.markers[0]); if (speech_marker->marker != 1 && speech_marker->marker != 0) { debug_printf("\nLoad error: Speech marker 0 is invalid (%d)!", speech_marker->marker); } if (speech_marker->position != 0) { debug_printf("Load error: Speech marker 0 has invalid position(%d)!", speech_marker->position); } for (i = 1; i < lip_info.marker_count; i++) { speech_marker = &(lip_info.markers[i]); prev_speech_marker = &(lip_info.markers[i - 1]); if (speech_marker->marker != 1 && speech_marker->marker != 0) { debug_printf("\nLoad error: Speech marker %d is invalid (%d)!", i, speech_marker->marker); } if (speech_marker->position < prev_speech_marker->position) { debug_printf("Load error: Speech marker %d has invalid position(%d)!", i, speech_marker->position); } } } if (stream != NULL) { db_fclose(stream); } lip_info.field_38 = 0; lip_info.field_34 = 0; lip_info.field_48 = 0; lip_info.field_20 = 0; lip_info.field_3C = 50; lip_info.field_40 = 100; if (lip_info.version == 1) { lip_info.field_4 = 22528; } strcpy(lip_info.field_58, "ACM"); strcpy(lip_info.field_5C, "TXT"); strcpy(lip_info.field_60, "LIP"); lips_make_speech(); head_marker_current = 0; head_phoneme_current = lip_info.phonemes[0]; return 0; } // 0x47B5D0 static int lips_make_speech() { if (lip_info.field_14 != NULL) { mem_free(lip_info.field_14); lip_info.field_14 = NULL; } char path[MAX_PATH]; char* v1 = lips_fix_string(lip_info.field_50, sizeof(lip_info.field_50)); sprintf(path, "%s%s\\%s.%s", "SOUND\\SPEECH\\", lips_subdir_name, v1, "ACM"); if (lip_info.sound != NULL) { soundDelete(lip_info.sound); lip_info.sound = NULL; } lip_info.sound = soundAllocate(1, 8); if (lip_info.sound == NULL) { debug_printf("\nsoundAllocate falied in lips_make_speech!"); return -1; } if (soundSetFileIO(lip_info.sound, audioOpen, audioCloseFile, audioRead, NULL, audioSeek, NULL, audioFileSize)) { debug_printf("Ack!"); debug_printf("Error!"); } if (soundLoad(lip_info.sound, path)) { soundDelete(lip_info.sound); lip_info.sound = NULL; debug_printf("lips_make_speech: soundLoad failed with path "); debug_printf("%s -- file probably doesn't exist.\n", path); return -1; } lip_info.field_34 = 8 * (lip_info.field_1C / lip_info.marker_count); return 0; } // 0x47B730 int lips_free_speech() { if (lip_info.field_14 != NULL) { mem_free(lip_info.field_14); lip_info.field_14 = NULL; } if (lip_info.sound != NULL) { // NOTE: Uninline. lips_stop_speech(); soundDelete(lip_info.sound); lip_info.sound = NULL; } if (lip_info.phonemes != NULL) { mem_free(lip_info.phonemes); lip_info.phonemes = NULL; } if (lip_info.markers != NULL) { mem_free(lip_info.markers); lip_info.markers = NULL; } return 0; } ================================================ FILE: src/game/lip_sync.h ================================================ #ifndef FALLOUT_GAME_LIP_SYNC_H_ #define FALLOUT_GAME_LIP_SYNC_H_ #include #include #include "plib/db/db.h" #include "int/sound.h" #define PHONEME_COUNT (42) typedef enum LipsFlags { LIPS_FLAG_0x01 = 0x01, LIPS_FLAG_0x02 = 0x02, } LipsFlags; typedef struct SpeechMarker { int marker; int position; } SpeechMarker; typedef struct LipsData { int version; int field_4; int flags; Sound* sound; int field_10; void* field_14; unsigned char* phonemes; int field_1C; int field_20; int phoneme_count; int field_28; int marker_count; SpeechMarker* markers; int field_34; int field_38; int field_3C; int field_40; int field_44; int field_48; int field_4C; char field_50[8]; char field_58[4]; char field_5C[4]; char field_60[4]; char field_64[260]; } LipsData; extern unsigned char head_phoneme_current; extern unsigned char head_phoneme_drawn; extern int head_marker_current; extern bool lips_draw_head; extern LipsData lip_info; extern int speechStartTime; void lips_bkg_proc(); int lips_play_speech(); int lips_load_file(const char* audioFileName, const char* headFileName); int lips_free_speech(); #endif /* FALLOUT_GAME_LIP_SYNC_H_ */ ================================================ FILE: src/game/loadsave.c ================================================ #include "game/loadsave.h" #include #include #include #include #include #include "game/automap.h" #include "game/editor.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "game/bmpdlog.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/gz.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/options.h" #include "game/perk.h" #include "game/pipboy.h" #include "game/proto.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "game/trait.h" #include "game/version.h" #include "plib/gnw/gnw.h" #include "game/wordwrap.h" #include "game/worldmap.h" #define LOAD_SAVE_SIGNATURE "FALLOUT SAVE FILE" #define LOAD_SAVE_DESCRIPTION_LENGTH 30 #define LOAD_SAVE_HANDLER_COUNT 27 #define LSGAME_MSG_NAME "LSGAME.MSG" #define LS_WINDOW_WIDTH 640 #define LS_WINDOW_HEIGHT 480 #define LS_PREVIEW_WIDTH 224 #define LS_PREVIEW_HEIGHT 133 #define LS_PREVIEW_SIZE ((LS_PREVIEW_WIDTH) * (LS_PREVIEW_HEIGHT)) #define LS_COMMENT_WINDOW_X 169 #define LS_COMMENT_WINDOW_Y 116 typedef int LoadGameHandler(File* stream); typedef int SaveGameHandler(File* stream); typedef enum LoadSaveWindowType { LOAD_SAVE_WINDOW_TYPE_SAVE_GAME, LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT, LOAD_SAVE_WINDOW_TYPE_LOAD_GAME, LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU, LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_LOAD_SLOT, } LoadSaveWindowType; typedef enum LoadSaveSlotState { SLOT_STATE_EMPTY, SLOT_STATE_OCCUPIED, SLOT_STATE_ERROR, SLOT_STATE_UNSUPPORTED_VERSION, } LoadSaveSlotState; typedef enum LoadSaveScrollDirection { LOAD_SAVE_SCROLL_DIRECTION_NONE, LOAD_SAVE_SCROLL_DIRECTION_UP, LOAD_SAVE_SCROLL_DIRECTION_DOWN, } LoadSaveScrollDirection; typedef struct LoadSaveSlotData { char signature[24]; short versionMinor; short versionMajor; // TODO: The type is probably char, but it's read with the same function as // reading unsigned chars, which in turn probably result of collapsing // reading functions. unsigned char versionRelease; char characterName[32]; char description[LOAD_SAVE_DESCRIPTION_LENGTH]; short fileMonth; short fileDay; short fileYear; int fileTime; short gameMonth; short gameDay; short gameYear; int gameTime; short elevation; short map; char fileName[16]; } LoadSaveSlotData; typedef enum LoadSaveFrm { LOAD_SAVE_FRM_BACKGROUND, LOAD_SAVE_FRM_BOX, LOAD_SAVE_FRM_PREVIEW_COVER, LOAD_SAVE_FRM_RED_BUTTON_PRESSED, LOAD_SAVE_FRM_RED_BUTTON_NORMAL, LOAD_SAVE_FRM_ARROW_DOWN_NORMAL, LOAD_SAVE_FRM_ARROW_DOWN_PRESSED, LOAD_SAVE_FRM_ARROW_UP_NORMAL, LOAD_SAVE_FRM_ARROW_UP_PRESSED, LOAD_SAVE_FRM_COUNT, } LoadSaveFrm; static int QuickSnapShot(); static int LSGameStart(int windowType); static int LSGameEnd(int windowType); static int SaveSlot(); static int LoadSlot(int slot); static int SaveHeader(int slot); static int LoadHeader(int slot); static int GetSlotList(); static void ShowSlotList(int a1); static void DrawInfoBox(int a1); static int LoadTumbSlot(int a1); static int GetComment(int a1); static int get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* description, int maxLength, int x, int y, int textColor, int backgroundColor, int flags); static int DummyFunc(File* stream); static int PrepLoad(File* stream); static int EndLoad(File* stream); static int GameMap2Slot(File* stream); static int SlotMap2Game(File* stream); static int mygets(char* dest, File* stream); static int copy_file(const char* a1, const char* a2); static int SaveBackup(); static int RestoreSave(); static int LoadObjDudeCid(File* stream); static int SaveObjDudeCid(File* stream); static int EraseSave(); // 0x47B7C0 static const int lsgrphs[LOAD_SAVE_FRM_COUNT] = { 237, // lsgame.frm - load/save game 238, // lsgbox.frm - load/save game 239, // lscover.frm - load/save game 9, // lilreddn.frm - little red button down 8, // lilredup.frm - little red button up 181, // dnarwoff.frm - character editor 182, // dnarwon.frm - character editor 199, // uparwoff.frm - character editor 200, // uparwon.frm - character editor }; // 0x5193B8 static int slot_cursor = 0; // 0x5193BC static bool quick_done = false; // 0x5193C0 static bool bk_enable = false; // 0x5193C4 static int map_backup_count = -1; // 0x5193C8 static int automap_db_flag = 0; // 0x5193CC static char* patches = NULL; // 0x5193D0 static char emgpath[] = "\\FALLOUT\\CD\\DATA\\SAVEGAME"; // 0x5193EC static SaveGameHandler* master_save_list[LOAD_SAVE_HANDLER_COUNT] = { DummyFunc, SaveObjDudeCid, scr_game_save, GameMap2Slot, scr_game_save, obj_save_dude, critter_save, critter_kill_count_save, skill_save, roll_save, perk_save, combat_save, combat_ai_save, stat_save, item_save, trait_save, automap_save, save_options, editor_save, wmWorldMap_save, save_pipboy, gmovie_save, skill_use_slot_save, partyMemberSave, queue_save, intface_save, DummyFunc, }; // 0x519458 static LoadGameHandler* master_load_list[LOAD_SAVE_HANDLER_COUNT] = { PrepLoad, LoadObjDudeCid, scr_game_load, SlotMap2Game, scr_game_load2, obj_load_dude, critter_load, critter_kill_count_load, skill_load, roll_load, perk_load, combat_load, combat_ai_load, stat_load, item_load, trait_load, automap_load, load_options, editor_load, wmWorldMap_load, load_pipboy, gmovie_load, skill_use_slot_load, partyMemberLoad, queue_load, intface_load, EndLoad, }; // 0x5194C4 static int loadingGame = 0; // 0x613CE0 static Size ginfo[LOAD_SAVE_FRM_COUNT]; // lsgame.msg // // 0x613D28 static MessageList lsgame_msgfl; // 0x613D30 static LoadSaveSlotData LSData[10]; // 0x614280 static int LSstatus[10]; // 0x6142A8 static unsigned char* thumbnail_image[2]; // 0x6142B0 static MessageListItem lsgmesg; // 0x6142C0 static int dbleclkcntr; // 0x6142C4 static int lsgwin; // 0x6142C8 static unsigned char* lsbmp[LOAD_SAVE_FRM_COUNT]; // 0x6142EC static unsigned char* snapshot; // 0x6142F0 static char str2[MAX_PATH]; // 0x6143F4 static char str0[MAX_PATH]; // 0x6144F8 static char str1[MAX_PATH]; // 0x6145FC static char str[MAX_PATH]; // 0x614700 static unsigned char* lsgbuf; // 0x614704 static char gmpath[MAX_PATH]; // 0x614808 static File* flptr; // 0x61480C static int ls_error_code; // 0x614810 static int fontsave; // 0x614814 static CacheEntry* grphkey[LOAD_SAVE_FRM_COUNT]; // 0x47B7E4 void InitLoadSave() { quick_done = false; slot_cursor = 0; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patches)) { debug_printf("\nLOADSAVE: Error reading patches config variable! Using default.\n"); patches = emgpath; } MapDirErase("MAPS\\", "SAV"); MapDirErase("PROTO\\CRITTERS\\", "PRO"); MapDirErase("PROTO\\ITEMS\\", "PRO"); } // 0x47B85C void ResetLoadSave() { MapDirErase("MAPS\\", "SAV"); MapDirErase("PROTO\\CRITTERS\\", "PRO"); MapDirErase("PROTO\\ITEMS\\", "PRO"); } // SaveGame // 0x47B88C int SaveGame(int mode) { MessageListItem messageListItem; ls_error_code = 0; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patches)) { debug_printf("\nLOADSAVE: Error reading patches config variable! Using default.\n"); patches = emgpath; } if (mode == LOAD_SAVE_MODE_QUICK && quick_done) { sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); strcat(gmpath, "SAVE.DAT"); flptr = db_fopen(gmpath, "rb"); if (flptr != NULL) { LoadHeader(slot_cursor); db_fclose(flptr); } thumbnail_image[1] = NULL; int v6 = QuickSnapShot(); if (v6 == 1) { int v7 = SaveSlot(); if (v7 != -1) { v6 = v7; } } if (thumbnail_image[1] != NULL) { mem_free(snapshot); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); if (v6 != -1) { return 1; } if (!message_init(&lsgame_msgfl)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "LSGAME.MSG"); if (!message_load(&lsgame_msgfl, path)) { return -1; } gsound_play_sfx_file("iisxxxx1"); // Error saving game! strcpy(str0, getmsg(&lsgame_msgfl, &messageListItem, 132)); // Unable to save game. strcpy(str1, getmsg(&lsgame_msgfl, &messageListItem, 133)); const char* body[] = { str1, }; dialog_out(str0, body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); message_exit(&lsgame_msgfl); return -1; } quick_done = false; int windowType = mode == LOAD_SAVE_MODE_QUICK ? LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT : LOAD_SAVE_WINDOW_TYPE_SAVE_GAME; if (LSGameStart(windowType) == -1) { debug_printf("\nLOADSAVE: ** Error loading save game screen data! **\n"); return -1; } if (GetSlotList() == -1) { win_draw(lsgwin); gsound_play_sfx_file("iisxxxx1"); // Error loading save game list! strcpy(str0, getmsg(&lsgame_msgfl, &messageListItem, 106)); // Save game directory: strcpy(str1, getmsg(&lsgame_msgfl, &messageListItem, 107)); sprintf(str2, "\"%s\\\"", "SAVEGAME"); // TODO: Check. strcpy(str2, getmsg(&lsgame_msgfl, &messageListItem, 108)); const char* body[] = { str1, str2, }; dialog_out(str0, body, 2, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); LSGameEnd(0); return -1; } switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: case SLOT_STATE_UNSUPPORTED_VERSION: buf_to_buf(thumbnail_image[1], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } ShowSlotList(0); DrawInfoBox(slot_cursor); win_draw(lsgwin); dbleclkcntr = 24; int rc = -1; int doubleClickSlot = -1; while (rc == -1) { unsigned int tick = get_time(); int keyCode = get_input(); bool selectionChanged = false; int scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE; if (keyCode == KEY_ESCAPE || keyCode == 501 || game_user_wants_to_quit != 0) { rc = 0; } else { switch (keyCode) { case KEY_ARROW_UP: slot_cursor -= 1; if (slot_cursor < 0) { slot_cursor = 0; } selectionChanged = true; doubleClickSlot = -1; break; case KEY_ARROW_DOWN: slot_cursor += 1; if (slot_cursor > 9) { slot_cursor = 9; } selectionChanged = true; doubleClickSlot = -1; break; case KEY_HOME: slot_cursor = 0; selectionChanged = true; doubleClickSlot = -1; break; case KEY_END: slot_cursor = 9; selectionChanged = true; doubleClickSlot = -1; break; case 506: scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_UP; break; case 504: scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_DOWN; break; case 502: if (1) { int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); slot_cursor = (mouseY - 79) / (3 * text_height() + 4); if (slot_cursor < 0) { slot_cursor = 0; } if (slot_cursor > 9) { slot_cursor = 9; } selectionChanged = true; if (slot_cursor == doubleClickSlot) { keyCode = 500; gsound_play_sfx_file("ib1p1xx1"); } doubleClickSlot = slot_cursor; scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE; } break; case KEY_CTRL_Q: case KEY_CTRL_X: case KEY_F10: game_quit_with_confirm(); if (game_user_wants_to_quit != 0) { rc = 0; } break; case KEY_PLUS: case KEY_EQUAL: IncGamma(); break; case KEY_MINUS: case KEY_UNDERSCORE: DecGamma(); break; case KEY_RETURN: keyCode = 500; break; } } if (keyCode == 500) { if (LSstatus[slot_cursor] == SLOT_STATE_OCCUPIED) { rc = 1; // Save game already exists, overwrite? const char* title = getmsg(&lsgame_msgfl, &lsgmesg, 131); if (dialog_out(title, NULL, 0, 169, 131, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_YES_NO) == 0) { rc = -1; } } else { rc = 1; } selectionChanged = true; scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE; } if (scrollDirection) { unsigned int scrollVelocity = 4; bool isScrolling = false; int scrollCounter = 0; do { unsigned int start = get_time(); scrollCounter += 1; if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) { isScrolling = true; if (scrollCounter > 14.4) { scrollVelocity += 1; if (scrollVelocity > 24) { scrollVelocity = 24; } } if (scrollDirection == LOAD_SAVE_SCROLL_DIRECTION_UP) { slot_cursor -= 1; if (slot_cursor < 0) { slot_cursor = 0; } } else { slot_cursor += 1; if (slot_cursor > 9) { slot_cursor = 9; } } // TODO: Does not check for unsupported version error like // other switches do. switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: buf_to_buf(thumbnail_image[1], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME); DrawInfoBox(slot_cursor); win_draw(lsgwin); } if (scrollCounter > 14.4) { while (elapsed_time(start) < 1000 / scrollVelocity) { } } else { while (elapsed_time(start) < 1000 / 24) { } } keyCode = get_input(); } while (keyCode != 505 && keyCode != 503); } else { if (selectionChanged) { switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: case SLOT_STATE_UNSUPPORTED_VERSION: buf_to_buf(thumbnail_image[1], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } DrawInfoBox(slot_cursor); ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME); } win_draw(lsgwin); dbleclkcntr -= 1; if (dbleclkcntr == 0) { dbleclkcntr = 24; doubleClickSlot = -1; } while (elapsed_time(tick) < 1000 / 24) { } } if (rc == 1) { int v50 = GetComment(slot_cursor); if (v50 == -1) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); gsound_play_sfx_file("iisxxxx1"); debug_printf("\nLOADSAVE: ** Error getting save file comment **\n"); // Error saving game! strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 132)); // Unable to save game. strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 133)); const char* body[1] = { str1, }; dialog_out(str0, body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); rc = -1; } else if (v50 == 0) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); rc = -1; } else if (v50 == 1) { if (SaveSlot() == -1) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); gsound_play_sfx_file("iisxxxx1"); // Error saving game! strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 132)); // Unable to save game. strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 133)); rc = -1; const char* body[1] = { str1, }; dialog_out(str0, body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); if (GetSlotList() == -1) { win_draw(lsgwin); gsound_play_sfx_file("iisxxxx1"); // Error loading save agme list! strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 106)); // Save game directory: strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 107)); sprintf(str2, "\"%s\\\"", "SAVEGAME"); char text[260]; // Doesn't exist or is corrupted. strcpy(text, getmsg(&lsgame_msgfl, &lsgmesg, 107)); const char* body[2] = { str1, str2, }; dialog_out(str0, body, 2, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE); LSGameEnd(0); return -1; } switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: case SLOT_STATE_UNSUPPORTED_VERSION: buf_to_buf(thumbnail_image[1], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } ShowSlotList(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME); DrawInfoBox(slot_cursor); win_draw(lsgwin); dbleclkcntr = 24; } } } } gmouse_set_cursor(MOUSE_CURSOR_ARROW); LSGameEnd(LOAD_SAVE_WINDOW_TYPE_SAVE_GAME); tile_refresh_display(); if (mode == LOAD_SAVE_MODE_QUICK) { if (rc == 1) { quick_done = true; } } return rc; } // 0x47C5B4 static int QuickSnapShot() { snapshot = (unsigned char*)mem_malloc(LS_PREVIEW_SIZE); if (snapshot == NULL) { return -1; } bool gameMouseWasVisible = gmouse_3d_is_on(); if (gameMouseWasVisible) { gmouse_3d_off(); } mouse_hide(); tile_refresh_display(); mouse_show(); if (gameMouseWasVisible) { gmouse_3d_on(); } unsigned char* windowBuffer = win_get_buf(display_win); cscale(windowBuffer, 640, 380, 640, snapshot, LS_PREVIEW_WIDTH, LS_PREVIEW_HEIGHT, LS_PREVIEW_WIDTH); thumbnail_image[1] = snapshot; return 1; } // LoadGame // 0x47C640 int LoadGame(int mode) { MessageListItem messageListItem; const char* body[] = { str1, str2, }; ls_error_code = 0; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &patches)) { debug_printf("\nLOADSAVE: Error reading patches config variable! Using default.\n"); patches = emgpath; } if (mode == LOAD_SAVE_MODE_QUICK && quick_done) { int quickSaveWindowX = 0; int quickSaveWindowY = 0; int window = win_add(quickSaveWindowX, quickSaveWindowY, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (window != -1) { unsigned char* windowBuffer = win_get_buf(window); buf_fill(windowBuffer, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, LS_WINDOW_WIDTH, colorTable[0]); win_draw(window); } if (LoadSlot(slot_cursor) != -1) { if (window != -1) { win_delete(window); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); return 1; } if (!message_init(&lsgame_msgfl)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s\\%s", msg_path, "LSGAME.MSG"); if (!message_load(&lsgame_msgfl, path)) { return -1; } if (window != -1) { win_delete(window); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); gsound_play_sfx_file("iisxxxx1"); strcpy(str0, getmsg(&lsgame_msgfl, &messageListItem, 134)); strcpy(str1, getmsg(&lsgame_msgfl, &messageListItem, 135)); dialog_out(str0, body, 1, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); message_exit(&lsgame_msgfl); map_new_map(); game_user_wants_to_quit = 2; return -1; } quick_done = false; int windowType; switch (mode) { case LOAD_SAVE_MODE_FROM_MAIN_MENU: windowType = LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU; break; case LOAD_SAVE_MODE_NORMAL: windowType = LOAD_SAVE_WINDOW_TYPE_LOAD_GAME; break; case LOAD_SAVE_MODE_QUICK: windowType = LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_LOAD_SLOT; break; default: assert(false && "Should be unreachable"); } if (LSGameStart(windowType) == -1) { debug_printf("\nLOADSAVE: ** Error loading save game screen data! **\n"); return -1; } if (GetSlotList() == -1) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); win_draw(lsgwin); gsound_play_sfx_file("iisxxxx1"); strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 106)); strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 107)); sprintf(str2, "\"%s\\\"", "SAVEGAME"); dialog_out(str0, body, 2, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); LSGameEnd(windowType); return -1; } switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: case SLOT_STATE_UNSUPPORTED_VERSION: buf_to_buf(lsbmp[LOAD_SAVE_FRM_PREVIEW_COVER], ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, lsgbuf + LS_WINDOW_WIDTH * 39 + 340, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } ShowSlotList(2); DrawInfoBox(slot_cursor); win_draw(lsgwin); dbleclkcntr = 24; int rc = -1; int doubleClickSlot = -1; while (rc == -1) { unsigned int time = get_time(); int keyCode = get_input(); bool selectionChanged = false; int scrollDirection = 0; if (keyCode == KEY_ESCAPE || keyCode == 501 || game_user_wants_to_quit != 0) { rc = 0; } else { switch (keyCode) { case KEY_ARROW_UP: if (--slot_cursor < 0) { slot_cursor = 0; } selectionChanged = true; doubleClickSlot = -1; break; case KEY_ARROW_DOWN: if (++slot_cursor > 9) { slot_cursor = 9; } selectionChanged = true; doubleClickSlot = -1; break; case KEY_HOME: slot_cursor = 0; selectionChanged = true; doubleClickSlot = -1; break; case KEY_END: slot_cursor = 9; selectionChanged = true; doubleClickSlot = -1; break; case 506: scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_UP; break; case 504: scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_DOWN; break; case 502: if (1) { int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int clickedSlot = (mouseY - 79) / (3 * text_height() + 4); if (clickedSlot < 0) { clickedSlot = 0; } else if (clickedSlot > 9) { clickedSlot = 9; } slot_cursor = clickedSlot; if (clickedSlot == doubleClickSlot) { keyCode = 500; gsound_play_sfx_file("ib1p1xx1"); } selectionChanged = true; scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE; doubleClickSlot = slot_cursor; } break; case KEY_MINUS: case KEY_UNDERSCORE: DecGamma(); break; case KEY_EQUAL: case KEY_PLUS: IncGamma(); break; case KEY_RETURN: keyCode = 500; break; case KEY_CTRL_Q: case KEY_CTRL_X: case KEY_F10: game_quit_with_confirm(); if (game_user_wants_to_quit != 0) { rc = 0; } break; } } if (keyCode == 500) { if (LSstatus[slot_cursor] != SLOT_STATE_EMPTY) { rc = 1; } else { rc = -1; } selectionChanged = true; scrollDirection = LOAD_SAVE_SCROLL_DIRECTION_NONE; } if (scrollDirection != LOAD_SAVE_SCROLL_DIRECTION_NONE) { unsigned int scrollVelocity = 4; bool isScrolling = false; int scrollCounter = 0; do { unsigned int start = get_time(); scrollCounter += 1; if ((!isScrolling && scrollCounter == 1) || (isScrolling && scrollCounter > 14.4)) { isScrolling = true; if (scrollCounter > 14.4) { scrollVelocity += 1; if (scrollVelocity > 24) { scrollVelocity = 24; } } if (scrollDirection == LOAD_SAVE_SCROLL_DIRECTION_UP) { slot_cursor -= 1; if (slot_cursor < 0) { slot_cursor = 0; } } else { slot_cursor += 1; if (slot_cursor > 9) { slot_cursor = 9; } } switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: case SLOT_STATE_UNSUPPORTED_VERSION: buf_to_buf(lsbmp[LOAD_SAVE_FRM_PREVIEW_COVER], ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, lsgbuf + LS_WINDOW_WIDTH * 39 + 340, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(lsbmp[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 39 + 340, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height, LS_WINDOW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 39 + 340, LS_WINDOW_WIDTH); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } ShowSlotList(2); DrawInfoBox(slot_cursor); win_draw(lsgwin); } if (scrollCounter > 14.4) { while (elapsed_time(start) < 1000 / scrollVelocity) { } } else { while (elapsed_time(start) < 1000 / 24) { } } keyCode = get_input(); } while (keyCode != 505 && keyCode != 503); } else { if (selectionChanged) { switch (LSstatus[slot_cursor]) { case SLOT_STATE_EMPTY: case SLOT_STATE_ERROR: case SLOT_STATE_UNSUPPORTED_VERSION: buf_to_buf(lsbmp[LOAD_SAVE_FRM_PREVIEW_COVER], ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, lsgbuf + LS_WINDOW_WIDTH * 39 + 340, LS_WINDOW_WIDTH); break; default: LoadTumbSlot(slot_cursor); buf_to_buf(lsbmp[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 39 + 340, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].width, ginfo[LOAD_SAVE_FRM_PREVIEW_COVER].height, LS_WINDOW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 39 + 340, LS_WINDOW_WIDTH); buf_to_buf(thumbnail_image[0], LS_PREVIEW_WIDTH - 1, LS_PREVIEW_HEIGHT - 1, LS_PREVIEW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 58 + 366, LS_WINDOW_WIDTH); break; } DrawInfoBox(slot_cursor); ShowSlotList(2); } win_draw(lsgwin); dbleclkcntr -= 1; if (dbleclkcntr == 0) { dbleclkcntr = 24; doubleClickSlot = -1; } while (elapsed_time(time) < 1000 / 24) { } } if (rc == 1) { switch (LSstatus[slot_cursor]) { case SLOT_STATE_UNSUPPORTED_VERSION: gsound_play_sfx_file("iisxxxx1"); strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 134)); strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 136)); strcpy(str2, getmsg(&lsgame_msgfl, &lsgmesg, 135)); dialog_out(str0, body, 2, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); rc = -1; break; case SLOT_STATE_ERROR: gsound_play_sfx_file("iisxxxx1"); strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 134)); strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 136)); dialog_out(str0, body, 1, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); rc = -1; break; default: if (LoadSlot(slot_cursor) == -1) { gmouse_set_cursor(MOUSE_CURSOR_ARROW); gsound_play_sfx_file("iisxxxx1"); strcpy(str0, getmsg(&lsgame_msgfl, &lsgmesg, 134)); strcpy(str1, getmsg(&lsgame_msgfl, &lsgmesg, 135)); dialog_out(str0, body, 1, 169, 116, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); map_new_map(); game_user_wants_to_quit = 2; rc = -1; } break; } } } LSGameEnd(mode == LOAD_SAVE_MODE_FROM_MAIN_MENU ? LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU : LOAD_SAVE_WINDOW_TYPE_LOAD_GAME); if (mode == LOAD_SAVE_MODE_QUICK) { if (rc == 1) { quick_done = true; } } return rc; } // 0x47D2E4 static int LSGameStart(int windowType) { fontsave = text_curr(); text_font(103); bk_enable = false; if (!message_init(&lsgame_msgfl)) { return -1; } sprintf(str, "%s%s", msg_path, LSGAME_MSG_NAME); if (!message_load(&lsgame_msgfl, str)) { return -1; } snapshot = (unsigned char*)mem_malloc(61632); if (snapshot == NULL) { message_exit(&lsgame_msgfl); text_font(fontsave); return -1; } thumbnail_image[0] = snapshot; thumbnail_image[1] = snapshot + LS_PREVIEW_SIZE; if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { bk_enable = map_disable_bk_processes(); } cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); if (windowType == LOAD_SAVE_WINDOW_TYPE_SAVE_GAME || windowType == LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT) { bool gameMouseWasVisible = gmouse_3d_is_on(); if (gameMouseWasVisible) { gmouse_3d_off(); } mouse_hide(); tile_refresh_display(); mouse_show(); if (gameMouseWasVisible) { gmouse_3d_on(); } unsigned char* windowBuf = win_get_buf(display_win); cscale(windowBuf, 640, 380, 640, thumbnail_image[1], LS_PREVIEW_WIDTH, LS_PREVIEW_HEIGHT, LS_PREVIEW_WIDTH); } for (int index = 0; index < LOAD_SAVE_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, lsgrphs[index], 0, 0, 0); lsbmp[index] = art_lock(fid, &(grphkey[index]), &(ginfo[index].width), &(ginfo[index].height)); if (lsbmp[index] == NULL) { while (--index >= 0) { art_ptr_unlock(grphkey[index]); } mem_free(snapshot); message_exit(&lsgame_msgfl); text_font(fontsave); if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { if (bk_enable) { map_enable_bk_processes(); } } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return -1; } } int lsWindowX = 0; int lsWindowY = 0; lsgwin = win_add(lsWindowX, lsWindowY, LS_WINDOW_WIDTH, LS_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (lsgwin == -1) { // FIXME: Leaking frms. mem_free(snapshot); message_exit(&lsgame_msgfl); text_font(fontsave); if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { if (bk_enable) { map_enable_bk_processes(); } } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return -1; } lsgbuf = win_get_buf(lsgwin); memcpy(lsgbuf, lsbmp[LOAD_SAVE_FRM_BACKGROUND], LS_WINDOW_WIDTH * LS_WINDOW_HEIGHT); int messageId; switch (windowType) { case LOAD_SAVE_WINDOW_TYPE_SAVE_GAME: // SAVE GAME messageId = 102; break; case LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_SAVE_SLOT: // PICK A QUICK SAVE SLOT messageId = 103; break; case LOAD_SAVE_WINDOW_TYPE_LOAD_GAME: case LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU: // LOAD GAME messageId = 100; break; case LOAD_SAVE_WINDOW_TYPE_PICK_QUICK_LOAD_SLOT: // PICK A QUICK LOAD SLOT messageId = 101; break; default: assert(false && "Should be unreachable"); } char* msg; msg = getmsg(&lsgame_msgfl, &lsgmesg, messageId); text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 27 + 48, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, colorTable[18979]); // DONE msg = getmsg(&lsgame_msgfl, &lsgmesg, 104); text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 348 + 410, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, colorTable[18979]); // CANCEL msg = getmsg(&lsgame_msgfl, &lsgmesg, 105); text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 348 + 515, msg, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, colorTable[18979]); int btn; btn = win_register_button(lsgwin, 391, 349, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, -1, -1, -1, 500, lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button(lsgwin, 495, 349, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, -1, -1, -1, 501, lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button(lsgwin, 35, 58, ginfo[LOAD_SAVE_FRM_ARROW_UP_PRESSED].width, ginfo[LOAD_SAVE_FRM_ARROW_UP_PRESSED].height, -1, 505, 506, 505, lsbmp[LOAD_SAVE_FRM_ARROW_UP_NORMAL], lsbmp[LOAD_SAVE_FRM_ARROW_UP_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } btn = win_register_button(lsgwin, 35, ginfo[LOAD_SAVE_FRM_ARROW_UP_PRESSED].height + 58, ginfo[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED].width, ginfo[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED].height, -1, 503, 504, 503, lsbmp[LOAD_SAVE_FRM_ARROW_DOWN_NORMAL], lsbmp[LOAD_SAVE_FRM_ARROW_DOWN_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } win_register_button(lsgwin, 55, 87, 230, 353, -1, -1, -1, 502, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); text_font(101); return 0; } // 0x47D824 static int LSGameEnd(int windowType) { win_delete(lsgwin); text_font(fontsave); message_exit(&lsgame_msgfl); for (int index = 0; index < LOAD_SAVE_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } mem_free(snapshot); if (windowType != LOAD_SAVE_WINDOW_TYPE_LOAD_GAME_FROM_MAIN_MENU) { if (bk_enable) { map_enable_bk_processes(); } } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return 0; } // 0x47D88C static int SaveSlot() { ls_error_code = 0; map_backup_count = -1; gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET); gsound_background_pause(); sprintf(gmpath, "%s\\%s", patches, "SAVEGAME"); mkdir(gmpath); sprintf(gmpath, "%s\\%s\\%s%.2d", patches, "SAVEGAME", "SLOT", slot_cursor + 1); mkdir(gmpath); strcat(gmpath, "\\proto"); mkdir(gmpath); char* protoBasePath = gmpath + strlen(gmpath); strcpy(protoBasePath, "\\critters"); mkdir(gmpath); strcpy(protoBasePath, "\\items"); mkdir(gmpath); if (SaveBackup() == -1) { debug_printf("\nLOADSAVE: Warning, can't backup save file!\n"); } sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); strcat(gmpath, "SAVE.DAT"); debug_printf("\nLOADSAVE: Save name: %s\n", gmpath); flptr = db_fopen(gmpath, "wb"); if (flptr == NULL) { debug_printf("\nLOADSAVE: ** Error opening save game for writing! **\n"); RestoreSave(); sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); MapDirErase(gmpath, "BAK"); partyMemberUnPrepSave(); gsound_background_unpause(); return -1; } long pos = db_ftell(flptr); if (SaveHeader(slot_cursor) == -1) { debug_printf("\nLOADSAVE: ** Error writing save game header! **\n"); debug_printf("LOADSAVE: Save file header size written: %d bytes.\n", db_ftell(flptr) - pos); db_fclose(flptr); RestoreSave(); sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); MapDirErase(gmpath, "BAK"); partyMemberUnPrepSave(); gsound_background_unpause(); return -1; } for (int index = 0; index < LOAD_SAVE_HANDLER_COUNT; index++) { long pos = db_ftell(flptr); SaveGameHandler* handler = master_save_list[index]; if (handler(flptr) == -1) { debug_printf("\nLOADSAVE: ** Error writing save function #%d data! **\n", index); db_fclose(flptr); RestoreSave(); sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); MapDirErase(gmpath, "BAK"); partyMemberUnPrepSave(); gsound_background_unpause(); return -1; } debug_printf("LOADSAVE: Save function #%d data size written: %d bytes.\n", index, db_ftell(flptr) - pos); } debug_printf("LOADSAVE: Total save data written: %ld bytes.\n", db_ftell(flptr)); db_fclose(flptr); sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); MapDirErase(gmpath, "BAK"); lsgmesg.num = 140; if (message_search(&lsgame_msgfl, &lsgmesg)) { display_print(lsgmesg.text); } else { debug_printf("\nError: Couldn't find LoadSave Message!"); } gsound_background_unpause(); return 0; } // 0x47DC60 int isLoadingGame() { return loadingGame; } // 0x47DC68 static int LoadSlot(int slot) { loadingGame = 1; if (isInCombat()) { intface_end_window_close(false); combat_over_from_load(); gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET); } sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); strcat(gmpath, "SAVE.DAT"); LoadSaveSlotData* ptr = &(LSData[slot]); debug_printf("\nLOADSAVE: Load name: %s\n", ptr->description); flptr = db_fopen(gmpath, "rb"); if (flptr == NULL) { debug_printf("\nLOADSAVE: ** Error opening load game file for reading! **\n"); loadingGame = 0; return -1; } long pos = db_ftell(flptr); if (LoadHeader(slot) == -1) { debug_printf("\nLOADSAVE: ** Error reading save game header! **\n"); db_fclose(flptr); game_reset(); loadingGame = 0; return -1; } debug_printf("LOADSAVE: Load file header size read: %d bytes.\n", db_ftell(flptr) - pos); for (int index = 0; index < LOAD_SAVE_HANDLER_COUNT; index += 1) { long pos = db_ftell(flptr); LoadGameHandler* handler = master_load_list[index]; if (handler(flptr) == -1) { debug_printf("\nLOADSAVE: ** Error reading load function #%d data! **\n", index); int v12 = db_ftell(flptr); debug_printf("LOADSAVE: Load function #%d data size read: %d bytes.\n", index, db_ftell(flptr) - pos); db_fclose(flptr); game_reset(); loadingGame = 0; return -1; } debug_printf("LOADSAVE: Load function #%d data size read: %d bytes.\n", index, db_ftell(flptr) - pos); } debug_printf("LOADSAVE: Total load data read: %ld bytes.\n", db_ftell(flptr)); db_fclose(flptr); sprintf(str, "%s\\", "MAPS"); MapDirErase(str, "BAK"); proto_dude_update_gender(); // Game Loaded. lsgmesg.num = 141; if (message_search(&lsgame_msgfl, &lsgmesg) == 1) { display_print(lsgmesg.text); } else { debug_printf("\nError: Couldn't find LoadSave Message!"); } loadingGame = 0; return 0; } // 0x47DF10 static int SaveHeader(int slot) { ls_error_code = 4; LoadSaveSlotData* ptr = &(LSData[slot]); strncpy(ptr->signature, LOAD_SAVE_SIGNATURE, 24); if (db_fwrite(ptr->signature, 1, 24, flptr) == -1) { return -1; } short temp[3]; temp[0] = VERSION_MAJOR; temp[1] = VERSION_MINOR; ptr->versionMinor = temp[0]; ptr->versionMajor = temp[1]; if (db_fwriteShortCount(flptr, temp, 2) == -1) { return -1; } ptr->versionRelease = VERSION_RELEASE; if (db_fwriteByte(flptr, VERSION_RELEASE) == -1) { return -1; } char* characterName = critter_name(obj_dude); strncpy(ptr->characterName, characterName, 32); if (db_fwrite(ptr->characterName, 32, 1, flptr) != 1) { return -1; } if (db_fwrite(ptr->description, 30, 1, flptr) != 1) { return -1; } time_t now = time(NULL); struct tm* local = localtime(&now); temp[0] = local->tm_mday; temp[1] = local->tm_mon + 1; temp[2] = local->tm_year + 1900; ptr->fileDay = temp[0]; ptr->fileMonth = temp[1]; ptr->fileYear = temp[2]; ptr->fileTime = local->tm_hour + local->tm_min; if (db_fwriteShortCount(flptr, temp, 3) == -1) { return -1; } if (db_fwriteLong(flptr, ptr->fileTime) == -1) { return -1; } int month; int day; int year; game_time_date(&month, &day, &year); temp[0] = month; temp[1] = day; temp[2] = year; ptr->gameTime = game_time(); if (db_fwriteShortCount(flptr, temp, 3) == -1) { return -1; } if (db_fwriteLong(flptr, ptr->gameTime) == -1) { return -1; } ptr->elevation = map_elevation; if (db_fwriteShort(flptr, ptr->elevation) == -1) { return -1; } ptr->map = map_get_index_number(); if (db_fwriteShort(flptr, ptr->map) == -1) { return -1; } char mapName[128]; strcpy(mapName, map_data.name); char* v1 = strmfe(str, mapName, "sav"); strncpy(ptr->fileName, v1, 16); if (db_fwrite(ptr->fileName, 16, 1, flptr) != 1) { return -1; } if (db_fwrite(thumbnail_image[1], LS_PREVIEW_SIZE, 1, flptr) != 1) { return -1; } memset(mapName, 0, 128); if (db_fwrite(mapName, 1, 128, flptr) != 128) { return -1; } ls_error_code = 0; return 0; } // 0x47E2E4 static int LoadHeader(int slot) { ls_error_code = 3; LoadSaveSlotData* ptr = &(LSData[slot]); if (db_fread(ptr->signature, 1, 24, flptr) != 24) { return -1; } if (strncmp(ptr->signature, LOAD_SAVE_SIGNATURE, 18) != 0) { debug_printf("\nLOADSAVE: ** Invalid save file on load! **\n"); ls_error_code = 2; return -1; } short v8[3]; if (db_freadShortCount(flptr, v8, 2) == -1) { return -1; } ptr->versionMinor = v8[0]; ptr->versionMajor = v8[1]; if (db_freadByte(flptr, &(ptr->versionRelease)) == -1) { return -1; } if (ptr->versionMinor != 1 || ptr->versionMajor != 2 || ptr->versionRelease != 'R') { debug_printf("\nLOADSAVE: Load slot #%d Version: %d.%d%c\n", slot, ptr->versionMinor, ptr->versionMajor, ptr->versionRelease); ls_error_code = 1; return -1; } if (db_fread(ptr->characterName, 32, 1, flptr) != 1) { return -1; } if (db_fread(ptr->description, 30, 1, flptr) != 1) { return -1; } if (db_freadShortCount(flptr, v8, 3) == -1) { return -1; } ptr->fileMonth = v8[0]; ptr->fileDay = v8[1]; ptr->fileYear = v8[2]; if (db_freadLong(flptr, &(ptr->fileTime)) == -1) { return -1; } if (db_freadShortCount(flptr, v8, 3) == -1) { return -1; } ptr->gameMonth = v8[0]; ptr->gameDay = v8[1]; ptr->gameYear = v8[2]; if (db_freadLong(flptr, &(ptr->gameTime)) == -1) { return -1; } if (db_freadShort(flptr, &(ptr->elevation)) == -1) { return -1; } if (db_freadShort(flptr, &(ptr->map)) == -1) { return -1; } if (db_fread(ptr->fileName, 1, 16, flptr) != 16) { return -1; } if (db_fseek(flptr, LS_PREVIEW_SIZE, SEEK_CUR) != 0) { return -1; } if (db_fseek(flptr, 128, 1) != 0) { return -1; } ls_error_code = 0; return 0; } // 0x47E5D0 static int GetSlotList() { int index = 0; for (; index < 10; index += 1) { sprintf(str, "%s\\%s%.2d\\%s", "SAVEGAME", "SLOT", index + 1, "SAVE.DAT"); int fileSize; if (db_dir_entry(str, &fileSize) != 0) { LSstatus[index] = SLOT_STATE_EMPTY; } else { flptr = db_fopen(str, "rb"); if (flptr == NULL) { debug_printf("\nLOADSAVE: ** Error opening save game for reading! **\n"); return -1; } if (LoadHeader(index) == -1) { if (ls_error_code == 1) { debug_printf("LOADSAVE: ** save file #%d is an older version! **\n", slot_cursor); LSstatus[index] = SLOT_STATE_UNSUPPORTED_VERSION; } else { debug_printf("LOADSAVE: ** Save file #%d corrupt! **", index); LSstatus[index] = SLOT_STATE_ERROR; } } else { LSstatus[index] = SLOT_STATE_OCCUPIED; } db_fclose(flptr); } } return index; } // 0x47E6D8 static void ShowSlotList(int a1) { buf_fill(lsgbuf + LS_WINDOW_WIDTH * 87 + 55, 230, 353, LS_WINDOW_WIDTH, lsgbuf[LS_WINDOW_WIDTH * 86 + 55] & 0xFF); int y = 87; for (int index = 0; index < 10; index += 1) { int color = index == slot_cursor ? colorTable[32747] : colorTable[992]; const char* text = getmsg(&lsgame_msgfl, &lsgmesg, a1 != 0 ? 110 : 109); sprintf(str, "[ %s %.2d: ]", text, index + 1); text_to_buf(lsgbuf + LS_WINDOW_WIDTH * y + 55, str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); y += text_height(); switch (LSstatus[index]) { case SLOT_STATE_OCCUPIED: strcpy(str, LSData[index].description); break; case SLOT_STATE_EMPTY: // - EMPTY - text = getmsg(&lsgame_msgfl, &lsgmesg, 111); sprintf(str, " %s", text); break; case SLOT_STATE_ERROR: // - CORRUPT SAVE FILE - text = getmsg(&lsgame_msgfl, &lsgmesg, 112); sprintf(str, "%s", text); color = colorTable[32328]; break; case SLOT_STATE_UNSUPPORTED_VERSION: // - OLD VERSION - text = getmsg(&lsgame_msgfl, &lsgmesg, 113); sprintf(str, " %s", text); color = colorTable[32328]; break; } text_to_buf(lsgbuf + LS_WINDOW_WIDTH * y + 55, str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); y += 2 * text_height() + 4; } } // 0x47E8E0 static void DrawInfoBox(int a1) { buf_to_buf(lsbmp[LOAD_SAVE_FRM_BACKGROUND] + LS_WINDOW_WIDTH * 254 + 396, 164, 60, LS_WINDOW_WIDTH, lsgbuf + LS_WINDOW_WIDTH * 254 + 396, 640); unsigned char* dest; const char* text; int color = colorTable[992]; switch (LSstatus[a1]) { case SLOT_STATE_OCCUPIED: do { LoadSaveSlotData* ptr = &(LSData[a1]); text_to_buf(lsgbuf + LS_WINDOW_WIDTH * 254 + 396, ptr->characterName, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); int v4 = ptr->gameTime / 600; int minutes = v4 % 60; int v6 = 25 * (v4 / 60 % 24); int time = 4 * v6 + minutes; text = getmsg(&lsgame_msgfl, &lsgmesg, 116 + ptr->gameMonth); sprintf(str, "%.2d %s %.4d %.4d", ptr->gameDay, text, ptr->gameYear, time); int v2 = text_height(); text_to_buf(lsgbuf + LS_WINDOW_WIDTH * (256 + v2) + 397, str, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); const char* v22 = map_get_elev_idx(ptr->map, ptr->elevation); const char* v9 = map_get_short_name(ptr->map); sprintf(str, "%s %s", v9, v22); int y = v2 + 3 + v2 + 256; short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (word_wrap(str, 164, beginnings, &count) == 0) { for (int index = 0; index < count - 1; index += 1) { char* beginning = str + beginnings[index]; char* ending = str + beginnings[index + 1]; char c = *ending; *ending = '\0'; text_to_buf(lsgbuf + LS_WINDOW_WIDTH * y + 399, beginning, 164, LS_WINDOW_WIDTH, color); y += v2 + 2; } } } while (0); return; case SLOT_STATE_EMPTY: // Empty. text = getmsg(&lsgame_msgfl, &lsgmesg, 114); dest = lsgbuf + LS_WINDOW_WIDTH * 262 + 404; break; case SLOT_STATE_ERROR: // Error! text = getmsg(&lsgame_msgfl, &lsgmesg, 115); dest = lsgbuf + LS_WINDOW_WIDTH * 262 + 404; color = colorTable[32328]; break; case SLOT_STATE_UNSUPPORTED_VERSION: // Old version. text = getmsg(&lsgame_msgfl, &lsgmesg, 116); dest = lsgbuf + LS_WINDOW_WIDTH * 262 + 400; color = colorTable[32328]; break; default: assert(false && "Should be unreachable"); } text_to_buf(dest, text, LS_WINDOW_WIDTH, LS_WINDOW_WIDTH, color); } // 0x47EC48 static int LoadTumbSlot(int a1) { File* stream; int v2; v2 = LSstatus[slot_cursor]; if (v2 != 0 && v2 != 2 && v2 != 3) { sprintf(str, "%s\\%s%.2d\\%s", "SAVEGAME", "SLOT", slot_cursor + 1, "SAVE.DAT"); debug_printf(" Filename %s\n", str); stream = db_fopen(str, "rb"); if (stream == NULL) { debug_printf("\nLOADSAVE: ** (A) Error reading thumbnail #%d! **\n", a1); return -1; } if (db_fseek(stream, 131, SEEK_SET) != 0) { debug_printf("\nLOADSAVE: ** (B) Error reading thumbnail #%d! **\n", a1); db_fclose(stream); return -1; } if (db_fread(thumbnail_image[0], LS_PREVIEW_SIZE, 1, stream) != 1) { debug_printf("\nLOADSAVE: ** (C) Error reading thumbnail #%d! **\n", a1); db_fclose(stream); return -1; } db_fclose(stream); } return 0; } // 0x47ED5C static int GetComment(int a1) { int commentWindowX = LS_COMMENT_WINDOW_X; int commentWindowY = LS_COMMENT_WINDOW_Y; int window = win_add(commentWindowX, commentWindowY, ginfo[LOAD_SAVE_FRM_BOX].width, ginfo[LOAD_SAVE_FRM_BOX].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (window == -1) { return -1; } unsigned char* windowBuffer = win_get_buf(window); memcpy(windowBuffer, lsbmp[LOAD_SAVE_FRM_BOX], ginfo[LOAD_SAVE_FRM_BOX].height * ginfo[LOAD_SAVE_FRM_BOX].width); text_font(103); const char* msg; // DONE msg = getmsg(&lsgame_msgfl, &lsgmesg, 104); text_to_buf(windowBuffer + ginfo[LOAD_SAVE_FRM_BOX].width * 57 + 56, msg, ginfo[LOAD_SAVE_FRM_BOX].width, ginfo[LOAD_SAVE_FRM_BOX].width, colorTable[18979]); // CANCEL msg = getmsg(&lsgame_msgfl, &lsgmesg, 105); text_to_buf(windowBuffer + ginfo[LOAD_SAVE_FRM_BOX].width * 57 + 181, msg, ginfo[LOAD_SAVE_FRM_BOX].width, ginfo[LOAD_SAVE_FRM_BOX].width, colorTable[18979]); // DESCRIPTION msg = getmsg(&lsgame_msgfl, &lsgmesg, 130); char title[260]; strcpy(title, msg); int width = text_width(title); text_to_buf(windowBuffer + ginfo[LOAD_SAVE_FRM_BOX].width * 7 + (ginfo[LOAD_SAVE_FRM_BOX].width - width) / 2, title, ginfo[LOAD_SAVE_FRM_BOX].width, ginfo[LOAD_SAVE_FRM_BOX].width, colorTable[18979]); text_font(101); int btn; // DONE btn = win_register_button(window, 34, 58, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, -1, -1, -1, 507, lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (btn == -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // CANCEL btn = win_register_button(window, 160, 58, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].width, ginfo[LOAD_SAVE_FRM_RED_BUTTON_PRESSED].height, -1, -1, -1, 508, lsbmp[LOAD_SAVE_FRM_RED_BUTTON_NORMAL], lsbmp[LOAD_SAVE_FRM_RED_BUTTON_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (btn == -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } win_draw(window); char description[LOAD_SAVE_DESCRIPTION_LENGTH]; if (LSstatus[slot_cursor] == SLOT_STATE_OCCUPIED) { strncpy(description, LSData[a1].description, LOAD_SAVE_DESCRIPTION_LENGTH); } else { memset(description, '\0', LOAD_SAVE_DESCRIPTION_LENGTH); } int rc; if (get_input_str2(window, 507, 508, description, LOAD_SAVE_DESCRIPTION_LENGTH - 1, 24, 35, colorTable[992], lsbmp[LOAD_SAVE_FRM_BOX][ginfo[1].width * 35 + 24], 0) == 0) { strncpy(LSData[a1].description, description, LOAD_SAVE_DESCRIPTION_LENGTH); LSData[a1].description[LOAD_SAVE_DESCRIPTION_LENGTH - 1] = '\0'; rc = 1; } else { rc = 0; } win_delete(window); return rc; } // 0x47F084 static int get_input_str2(int win, int doneKeyCode, int cancelKeyCode, char* description, int maxLength, int x, int y, int textColor, int backgroundColor, int flags) { int cursorWidth = text_width("_") - 4; int windowWidth = win_width(win); int lineHeight = text_height(); unsigned char* windowBuffer = win_get_buf(win); if (maxLength > 255) { maxLength = 255; } char text[256]; strcpy(text, description); int textLength = strlen(text); text[textLength] = ' '; text[textLength + 1] = '\0'; int nameWidth = text_width(text); buf_fill(windowBuffer + windowWidth * y + x, nameWidth, lineHeight, windowWidth, backgroundColor); text_to_buf(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor); win_draw(win); int blinkCounter = 3; bool blink = false; int v1 = 0; int rc = 1; while (rc == 1) { int tick = get_time(); int keyCode = get_input(); if ((keyCode & 0x80000000) == 0) { v1++; } if (keyCode == doneKeyCode || keyCode == KEY_RETURN) { rc = 0; } else if (keyCode == cancelKeyCode || keyCode == KEY_ESCAPE) { rc = -1; } else { if ((keyCode == KEY_DELETE || keyCode == KEY_BACKSPACE) && textLength > 0) { buf_fill(windowBuffer + windowWidth * y + x, text_width(text), lineHeight, windowWidth, backgroundColor); // TODO: Probably incorrect, needs testing. if (v1 == 1) { textLength = 1; } text[textLength - 1] = ' '; text[textLength] = '\0'; text_to_buf(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor); textLength--; } else if ((keyCode >= KEY_FIRST_INPUT_CHARACTER && keyCode <= KEY_LAST_INPUT_CHARACTER) && textLength < maxLength) { if ((flags & 0x01) != 0) { if (!isdoschar(keyCode)) { break; } } buf_fill(windowBuffer + windowWidth * y + x, text_width(text), lineHeight, windowWidth, backgroundColor); text[textLength] = keyCode & 0xFF; text[textLength + 1] = ' '; text[textLength + 2] = '\0'; text_to_buf(windowBuffer + windowWidth * y + x, text, windowWidth, windowWidth, textColor); textLength++; win_draw(win); } } blinkCounter -= 1; if (blinkCounter == 0) { blinkCounter = 3; blink = !blink; int color = blink ? backgroundColor : textColor; buf_fill(windowBuffer + windowWidth * y + x + text_width(text) - cursorWidth, cursorWidth, lineHeight - 2, windowWidth, color); win_draw(win); } while (elapsed_time(tick) < 1000 / 24) { } } if (rc == 0) { text[textLength] = '\0'; strcpy(description, text); } return rc; } // 0x47F48C static int DummyFunc(File* stream) { return 0; } // 0x47F490 static int PrepLoad(File* stream) { game_reset(); gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET); map_data.name[0] = '\0'; gameTimeSetTime(LSData[slot_cursor].gameTime); return 0; } // 0x47F4C8 static int EndLoad(File* stream) { wmMapMusicStart(); critter_pc_set_name(LSData[slot_cursor].characterName); intface_redraw(); refresh_box_bar_win(); tile_refresh_display(); if (isInCombat()) { scripts_request_combat(NULL); } return 0; } // 0x47F510 static int GameMap2Slot(File* stream) { if (partyMemberPrepSave() == -1) { return -1; } if (map_save_in_game(false) == -1) { return -1; } for (int index = 1; index < partyMemberMaxCount; index += 1) { int pid = partyMemberPidList[index]; if (pid == -2) { continue; } char path[MAX_PATH]; if (proto_list_str(pid, path) != 0) { continue; } const char* critterItemPath = PID_TYPE(pid) == OBJ_TYPE_CRITTER ? "PROTO\\CRITTERS" : "PROTO\\ITEMS"; sprintf(str0, "%s\\%s\\%s", patches, critterItemPath, path); sprintf(str1, "%s\\%s\\%s%.2d\\%s\\%s", patches, "SAVEGAME", "SLOT", slot_cursor + 1, critterItemPath, path); if (gzcompress_file(str0, str1) == -1) { return -1; } } sprintf(str0, "%s\\*.%s", "MAPS", "SAV"); char** fileNameList; int fileNameListLength = db_get_file_list(str0, &fileNameList, 0, 0); if (fileNameListLength == -1) { return -1; } if (db_fwriteInt(stream, fileNameListLength) == -1) { db_free_file_list(&fileNameList, 0); return -1; } if (fileNameListLength == 0) { db_free_file_list(&fileNameList, 0); return -1; } sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); if (MapDirErase(gmpath, "SAV") == -1) { db_free_file_list(&fileNameList, 0); return -1; } sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); strmfe(str0, "AUTOMAP.DB", "SAV"); strcat(gmpath, str0); remove(gmpath); for (int index = 0; index < fileNameListLength; index += 1) { char* string = fileNameList[index]; if (db_fwrite(string, strlen(string) + 1, 1, stream) == -1) { db_free_file_list(&fileNameList, 0); return -1; } sprintf(str0, "%s\\%s\\%s", patches, "MAPS", string); sprintf(str1, "%s\\%s\\%s%.2d\\%s", patches, "SAVEGAME", "SLOT", slot_cursor + 1, string); if (gzcompress_file(str0, str1) == -1) { db_free_file_list(&fileNameList, 0); return -1; } } db_free_file_list(&fileNameList, 0); strmfe(str0, "AUTOMAP.DB", "SAV"); sprintf(str1, "%s\\%s\\%s%.2d\\%s", patches, "SAVEGAME", "SLOT", slot_cursor + 1, str0); sprintf(str0, "%s\\%s\\%s", patches, "MAPS", "AUTOMAP.DB"); if (gzcompress_file(str0, str1) == -1) { return -1; } sprintf(str0, "%s\\%s", "MAPS", "AUTOMAP.DB"); File* inStream = db_fopen(str0, "rb"); if (inStream == NULL) { return -1; } int fileSize = db_filelength(inStream); if (fileSize == -1) { db_fclose(inStream); return -1; } db_fclose(inStream); if (db_fwriteInt(stream, fileSize) == -1) { return -1; } if (partyMemberUnPrepSave() == -1) { return -1; } return 0; } // SlotMap2Game // 0x47F990 static int SlotMap2Game(File* stream) { debug_printf("LOADSAVE: in SlotMap2Game\n"); int fileNameListLength; if (db_freadInt(stream, &fileNameListLength) == -1) { debug_printf("LOADSAVE: returning 1\n"); return -1; } if (fileNameListLength == 0) { debug_printf("LOADSAVE: returning 2\n"); return -1; } sprintf(str0, "%s\\", "PROTO\\CRITTERS"); if (MapDirErase(str0, "PRO") == -1) { debug_printf("LOADSAVE: returning 3\n"); return -1; } sprintf(str0, "%s\\", "PROTO\\ITEMS"); if (MapDirErase(str0, "PRO") == -1) { debug_printf("LOADSAVE: returning 4\n"); return -1; } sprintf(str0, "%s\\", "MAPS"); if (MapDirErase(str0, "SAV") == -1) { debug_printf("LOADSAVE: returning 5\n"); return -1; } sprintf(str0, "%s\\%s\\%s", patches, "MAPS", "AUTOMAP.DB"); remove(str0); for (int index = 1; index < partyMemberMaxCount; index += 1) { int pid = partyMemberPidList[index]; if (pid != -2) { char protoPath[MAX_PATH]; if (proto_list_str(pid, protoPath) == 0) { const char* basePath = PID_TYPE(pid) == OBJ_TYPE_CRITTER ? "PROTO\\CRITTERS" : "PROTO\\ITEMS"; sprintf(str0, "%s\\%s\\%s", patches, basePath, protoPath); sprintf(str1, "%s\\%s\\%s%.2d\\%s\\%s", patches, "SAVEGAME", "SLOT", slot_cursor + 1, basePath, protoPath); if (gzdecompress_file(str1, str0) == -1) { debug_printf("LOADSAVE: returning 6\n"); return -1; } } } } for (int index = 0; index < fileNameListLength; index += 1) { char fileName[MAX_PATH]; if (mygets(fileName, stream) == -1) { break; } sprintf(str0, "%s\\%s\\%s%.2d\\%s", patches, "SAVEGAME", "SLOT", slot_cursor + 1, fileName); sprintf(str1, "%s\\%s\\%s", patches, "MAPS", fileName); if (gzdecompress_file(str0, str1) == -1) { debug_printf("LOADSAVE: returning 7\n"); return -1; } } const char* automapFileName = strmfe(str1, "AUTOMAP.DB", "SAV"); sprintf(str0, "%s\\%s\\%s%.2d\\%s", patches, "SAVEGAME", "SLOT", slot_cursor + 1, automapFileName); sprintf(str1, "%s\\%s\\%s", patches, "MAPS", "AUTOMAP.DB"); if (gzRealUncompressCopyReal_file(str0, str1) == -1) { debug_printf("LOADSAVE: returning 8\n"); return -1; } sprintf(str1, "%s\\%s", "MAPS", "AUTOMAP.DB"); int v12; if (db_freadInt(stream, &v12) == -1) { debug_printf("LOADSAVE: returning 9\n"); return -1; } if (map_load_in_game(LSData[slot_cursor].fileName) == -1) { debug_printf("LOADSAVE: returning 13\n"); return -1; } return 0; } // 0x47FE14 static int mygets(char* dest, File* stream) { int index = 14; while (true) { int c = db_fgetc(stream); if (c == -1) { return -1; } index -= 1; *dest = c & 0xFF; dest += 1; if (index == -1 || c == '\0') { break; } } if (index == 0) { return -1; } return 0; } // 0x47FE58 static int copy_file(const char* a1, const char* a2) { File* stream1; File* stream2; int length; int chunk_length; void* buf; int result; stream1 = NULL; stream2 = NULL; buf = NULL; result = -1; stream1 = db_fopen(a1, "rb"); if (stream1 == NULL) { goto out; } length = db_filelength(stream1); if (length == -1) { goto out; } stream2 = db_fopen(a2, "wb"); if (stream2 == NULL) { goto out; } buf = mem_malloc(0xFFFF); if (buf == NULL) { goto out; } while (length != 0) { chunk_length = min(length, 0xFFFF); if (db_fread(buf, chunk_length, 1, stream1) != 1) { break; } if (db_fwrite(buf, chunk_length, 1, stream2) != 1) { break; } length -= chunk_length; } if (length != 0) { goto out; } result = 0; out: if (stream1 != NULL) { db_fclose(stream1); } if (stream2 != NULL) { db_fclose(stream1); } if (buf != NULL) { mem_free(buf); } return result; } // InitLoadSave // 0x48000C void KillOldMaps() { char path[MAX_PATH]; sprintf(path, "%s\\", "MAPS"); MapDirErase(path, "SAV"); } // 0x480040 int MapDirErase(const char* relativePath, const char* extension) { char path[MAX_PATH]; sprintf(path, "%s*.%s", relativePath, extension); char** fileList; int fileListLength = db_get_file_list(path, &fileList, 0, 0); while (--fileListLength >= 0) { sprintf(path, "%s\\%s%s", patches, relativePath, fileList[fileListLength]); remove(path); } db_free_file_list(&fileList, 0); return 0; } // 0x4800C8 int MapDirEraseFile(const char* a1, const char* a2) { char path[MAX_PATH]; sprintf(path, "%s\\%s%s", patches, a1, a2); if (remove(path) != 0) { return -1; } return 0; } // 0x480104 static int SaveBackup() { debug_printf("\nLOADSAVE: Backing up save slot files..\n"); sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); strcpy(str0, gmpath); strcat(str0, "SAVE.DAT"); strmfe(str1, str0, "BAK"); File* stream1 = db_fopen(str0, "rb"); if (stream1 != NULL) { db_fclose(stream1); if (rename(str0, str1) != 0) { return -1; } } sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); sprintf(str0, "%s*.%s", gmpath, "SAV"); char** fileList; int fileListLength = db_get_file_list(str0, &fileList, 0, 0); if (fileListLength == -1) { return -1; } map_backup_count = fileListLength; sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); for (int index = fileListLength - 1; index >= 0; index--) { strcpy(str0, gmpath); strcat(str0, fileList[index]); strmfe(str1, str0, "BAK"); if (rename(str0, str1) != 0) { db_free_file_list(&fileList, 0); return -1; } } db_free_file_list(&fileList, 0); debug_printf("\nLOADSAVE: %d map files backed up.\n", fileListLength); sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); char* v1 = strmfe(str2, "AUTOMAP.DB", "SAV"); sprintf(str0, "%s\\%s", gmpath, v1); char* v2 = strmfe(str2, "AUTOMAP.DB", "BAK"); sprintf(str1, "%s\\%s", gmpath, v2); automap_db_flag = 0; File* stream2 = db_fopen(str0, "rb"); if (stream2 != NULL) { db_fclose(stream2); if (copy_file(str0, str1) == -1) { return -1; } automap_db_flag = 1; } return 0; } // 0x4803D8 static int RestoreSave() { debug_printf("\nLOADSAVE: Restoring save file backup...\n"); EraseSave(); sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); strcpy(str0, gmpath); strcat(str0, "SAVE.DAT"); strmfe(str1, str0, "BAK"); remove(str0); if (rename(str1, str0) != 0) { EraseSave(); return -1; } sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); sprintf(str0, "%s*.%s", gmpath, "BAK"); char** fileList; int fileListLength = db_get_file_list(str0, &fileList, 0, 0); if (fileListLength == -1) { return -1; } if (fileListLength != map_backup_count) { // FIXME: Probably leaks fileList. EraseSave(); return -1; } sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); for (int index = fileListLength - 1; index >= 0; index--) { strcpy(str0, gmpath); strcat(str0, fileList[index]); strmfe(str1, str0, "SAV"); remove(str1); if (rename(str0, str1) != 0) { // FIXME: Probably leaks fileList. EraseSave(); return -1; } } db_free_file_list(&fileList, 0); if (!automap_db_flag) { return 0; } sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); char* v1 = strmfe(str2, "AUTOMAP.DB", "BAK"); strcpy(str0, gmpath); strcat(str0, v1); char* v2 = strmfe(str2, "AUTOMAP.DB", "SAV"); strcpy(str1, gmpath); strcat(str1, v2); if (rename(str0, str1) != 0) { EraseSave(); return -1; } return 0; } // 0x480710 static int LoadObjDudeCid(File* stream) { int value; if (db_freadInt(stream, &value) == -1) { return -1; } obj_dude->cid = value; return 0; } // 0x480734 static int SaveObjDudeCid(File* stream) { return db_fwriteInt(stream, obj_dude->cid); } // 0x480754 static int EraseSave() { debug_printf("\nLOADSAVE: Erasing save(bad) slot...\n"); sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); strcpy(str0, gmpath); strcat(str0, "SAVE.DAT"); remove(str0); sprintf(gmpath, "%s\\%s%.2d\\", "SAVEGAME", "SLOT", slot_cursor + 1); sprintf(str0, "%s*.%s", gmpath, "SAV"); char** fileList; int fileListLength = db_get_file_list(str0, &fileList, 0, 0); if (fileListLength == -1) { return -1; } sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); for (int index = fileListLength - 1; index >= 0; index--) { strcpy(str0, gmpath); strcat(str0, fileList[index]); remove(str0); } db_free_file_list(&fileList, 0); sprintf(gmpath, "%s\\%s\\%s%.2d\\", patches, "SAVEGAME", "SLOT", slot_cursor + 1); char* v1 = strmfe(str1, "AUTOMAP.DB", "SAV"); strcpy(str0, gmpath); strcat(str0, v1); remove(str0); return 0; } ================================================ FILE: src/game/loadsave.h ================================================ #ifndef FALLOUT_GAME_LOADSAVE_H_ #define FALLOUT_GAME_LOADSAVE_H_ #include #define WIN32_LEAN_AND_MEAN #include #include "game/art.h" #include "plib/db/db.h" #include "plib/gnw/rect.h" #include "game/message.h" typedef enum LoadSaveMode { // Special case - loading game from main menu. LOAD_SAVE_MODE_FROM_MAIN_MENU, // Normal (full-screen) save/load screen. LOAD_SAVE_MODE_NORMAL, // Quick load/save. LOAD_SAVE_MODE_QUICK, } LoadSaveMode; void InitLoadSave(); void ResetLoadSave(); int SaveGame(int mode); int LoadGame(int mode); int isLoadingGame(); void KillOldMaps(); int MapDirErase(const char* path, const char* a2); int MapDirEraseFile(const char* a1, const char* a2); #endif /* FALLOUT_GAME_LOADSAVE_H_ */ ================================================ FILE: src/game/main.c ================================================ #include "main.h" // NOTE: Actual file name is unknown. Functions in this module do not present // in debug symbols from `mapper2.exe`. In OS X binary these functions appear // very far from the ones found in `mainmenu.c`, implying they are in separate // compilation unit. In Windows binary these functions appear between // `loadsave.c` and `mainmenu.c`. Based on the order it's file name should be // between these two, so `main.c` is a perfect candidate, but again, it's just a // guess. // // Function names and visibility scope are from in OS X binary. #include #include #include "game/amutex.h" #include "game/art.h" #include "game/select.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/credits.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/endgame.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/loadsave.h" #include "game/mainmenu.h" #include "game/map.h" #include "game/object.h" #include "game/options.h" #include "game/palette.h" #include "game/proto.h" #include "game/roll.h" #include "game/scripts.h" #include "game/selfrun.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #include "plib/gnw/intrface.h" #include "game/wordwrap.h" #include "game/worldmap.h" #define DEATH_WINDOW_WIDTH 640 #define DEATH_WINDOW_HEIGHT 480 static bool main_init_system(int argc, char** argv); static int main_reset_system(); static void main_exit_system(); static int main_load_new(char* fname); static int main_loadgame_new(); static void main_unload_new(); static void main_game_loop(); static bool main_selfrun_init(); static void main_selfrun_exit(); static void main_selfrun_record(); static void main_selfrun_play(); static void main_death_scene(); static void main_death_voiceover_callback(); static int mainDeathGrabTextFile(const char* fileName, char* dest); static int mainDeathWordWrap(char* text, int width, short* beginnings, short* count); // 0x5194C8 static char mainMap[] = "artemple.map"; // 0x5194D8 int main_game_paused = 0; // 0x5194DC static char** main_selfrun_list = NULL; // 0x5194E0 static int main_selfrun_count = 0; // 0x5194E4 static int main_selfrun_index = 0; // 0x5194E8 static bool main_show_death_scene = false; // 0x614838 static bool main_death_voiceover_done; // 0x48099C int RealMain(int argc, char** argv) { if (!autorun_mutex_create()) { return 1; } if (!main_init_system(argc, argv)) { return 1; } gmovie_play(MOVIE_IPLOGO, GAME_MOVIE_FADE_IN); gmovie_play(MOVIE_INTRO, 0); gmovie_play(MOVIE_CREDITS, 0); if (main_menu_create() == 0) { bool done = false; while (!done) { kb_clear(); gsound_background_play_level_music("07desert", 11); main_menu_show(1); mouse_show(); int mainMenuRc = main_menu_loop(); mouse_hide(); switch (mainMenuRc) { case MAIN_MENU_INTRO: main_menu_hide(true); gmovie_play(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC); gmovie_play(MOVIE_CREDITS, 0); break; case MAIN_MENU_NEW_GAME: main_menu_hide(true); main_menu_destroy(); if (select_character() == 2) { gmovie_play(MOVIE_ELDER, GAME_MOVIE_STOP_MUSIC); roll_set_seed(-1); main_load_new(mainMap); main_game_loop(); palette_fade_to(white_palette); // NOTE: Uninline. main_unload_new(); // NOTE: Uninline. main_reset_system(); if (main_show_death_scene != 0) { main_death_scene(); main_show_death_scene = 0; } } main_menu_create(); break; case MAIN_MENU_LOAD_GAME: if (1) { int win = win_add(0, 0, 640, 480, colorTable[0], WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); main_menu_hide(true); main_menu_destroy(); gsound_background_stop(); // NOTE: Uninline. main_loadgame_new(); loadColorTable("color.pal"); palette_fade_to(cmap); int loadGameRc = LoadGame(LOAD_SAVE_MODE_FROM_MAIN_MENU); if (loadGameRc == -1) { debug_printf("\n ** Error running LoadGame()! **\n"); } else if (loadGameRc != 0) { win_delete(win); win = -1; main_game_loop(); } palette_fade_to(white_palette); if (win != -1) { win_delete(win); } // NOTE: Uninline. main_unload_new(); // NOTE: Uninline. main_reset_system(); if (main_show_death_scene != 0) { main_death_scene(); main_show_death_scene = 0; } main_menu_create(); } break; case MAIN_MENU_TIMEOUT: debug_printf("Main menu timed-out\n"); // FALLTHROUGH case MAIN_MENU_SCREENSAVER: main_selfrun_play(); break; case MAIN_MENU_OPTIONS: main_menu_hide(false); mouse_show(); do_optionsFunc(112); gmouse_set_cursor(MOUSE_CURSOR_ARROW); mouse_show(); main_menu_show(0); break; case MAIN_MENU_CREDITS: main_menu_hide(true); credits("credits.txt", -1, false); break; case MAIN_MENU_QUOTES: // NOTE: There is a strange cmp at 0x480C50. Both operands are // zero, set before the loop and do not modify afterwards. For // clarity this condition is omitted. main_menu_hide(true); credits("quotes.txt", -1, true); break; case MAIN_MENU_EXIT: case -1: done = true; main_menu_hide(true); main_menu_destroy(); gsound_background_stop(); break; case MAIN_MENU_SELFRUN: main_selfrun_record(); break; } } } // NOTE: Uninline. main_exit_system(); autorun_mutex_destroy(); return 0; } // 0x480CC0 static bool main_init_system(int argc, char** argv) { if (game_init("FALLOUT II", false, 0, 0, argc, argv) == -1) { return false; } // NOTE: Uninline. main_selfrun_init(); return true; } // NOTE: Inlined. // // 0x480D0C static int main_reset_system() { game_reset(); return 1; } // NOTE: Inlined. // // 0x480D18 static void main_exit_system() { gsound_background_stop(); // NOTE: Uninline. main_selfrun_exit(); game_exit(); } // 0x480D4C static int main_load_new(char* mapFileName) { game_user_wants_to_quit = 0; main_show_death_scene = 0; obj_dude->flags &= ~OBJECT_FLAT; obj_turn_on(obj_dude, NULL); mouse_hide(); int win = win_add(0, 0, 640, 480, colorTable[0], WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); win_draw(win); loadColorTable("color.pal"); palette_fade_to(cmap); map_init(); gmouse_set_cursor(MOUSE_CURSOR_NONE); mouse_show(); map_load(mapFileName); wmMapMusicStart(); palette_fade_to(white_palette); win_delete(win); loadColorTable("color.pal"); palette_fade_to(cmap); return 0; } // NOTE: Inlined. // // 0x480DF8 static int main_loadgame_new() { game_user_wants_to_quit = 0; main_show_death_scene = 0; obj_dude->flags &= ~OBJECT_FLAT; obj_turn_on(obj_dude, NULL); mouse_hide(); map_init(); gmouse_set_cursor(MOUSE_CURSOR_NONE); mouse_show(); return 0; } // 0x480E34 static void main_unload_new() { obj_turn_off(obj_dude, NULL); map_exit(); } // 0x480E48 static void main_game_loop() { bool cursorWasHidden = mouse_hidden(); if (cursorWasHidden) { mouse_show(); } main_game_paused = 0; scr_enable(); while (game_user_wants_to_quit == 0) { int keyCode = get_input(); game_handle_input(keyCode, false); scripts_check_state(); map_check_state(); if (main_game_paused != 0) { main_game_paused = 0; } if ((obj_dude->data.critter.combat.results & (DAM_DEAD | DAM_KNOCKED_OUT)) != 0) { endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_DEATH); main_show_death_scene = 1; game_user_wants_to_quit = 2; } } scr_disable(); if (cursorWasHidden) { mouse_hide(); } } // NOTE: Inlined. // // 0x480EE4 static bool main_selfrun_init() { if (main_selfrun_list != NULL) { // NOTE: Uninline. main_selfrun_exit(); } if (selfrun_get_list(&main_selfrun_list, &main_selfrun_count) != 0) { return false; } main_selfrun_index = 0; return true; } // 0x480F38 static void main_selfrun_exit() { if (main_selfrun_list != NULL) { selfrun_free_list(&main_selfrun_list); } main_selfrun_count = 0; main_selfrun_index = 0; main_selfrun_list = NULL; } // 0x480F64 static void main_selfrun_record() { SelfrunData selfrunData; bool ready = false; char** fileList; int fileListLength = db_get_file_list("maps\\*.map", &fileList, 0, 0); if (fileListLength != 0) { int selectedFileIndex = win_list_select("Select Map", fileList, fileListLength, 0, 80, 80, 0x10000 | 0x100 | 4); if (selectedFileIndex != -1) { // NOTE: It's size is likely 13 chars (on par with SelfrunData // fields), but due to the padding it takes 16 chars on stack. char recordingName[SELFRUN_RECORDING_FILE_NAME_LENGTH]; recordingName[0] = '\0'; if (win_get_str(recordingName, sizeof(recordingName) - 2, "Enter name for recording (8 characters max, no extension):", 100, 100) == 0) { memset(&selfrunData, 0, sizeof(selfrunData)); if (selfrun_prep_recording(recordingName, fileList[selectedFileIndex], &selfrunData) == 0) { ready = true; } } } db_free_file_list(&fileList, 0); } if (ready) { main_menu_hide(true); main_menu_destroy(); gsound_background_stop(); roll_set_seed(0xBEEFFEED); // NOTE: Uninline. main_reset_system(); proto_dude_init("premade\\combat.gcd"); main_load_new(selfrunData.mapFileName); selfrun_recording_loop(&selfrunData); palette_fade_to(white_palette); // NOTE: Uninline. main_unload_new(); // NOTE: Uninline. main_reset_system(); main_menu_create(); // NOTE: Uninline. main_selfrun_init(); } } // 0x48109C static void main_selfrun_play() { // A switch to pick selfrun vs. intro video for screensaver: // - `false` - will play next selfrun recording // - `true` - will play intro video // // This value will alternate on every attempt, even if there are no selfrun // recordings. // // 0x5194EC static bool toggle = false; if (!toggle && main_selfrun_count > 0) { SelfrunData selfrunData; if (selfrun_prep_playback(main_selfrun_list[main_selfrun_index], &selfrunData) == 0) { main_menu_hide(true); main_menu_destroy(); gsound_background_stop(); roll_set_seed(0xBEEFFEED); // NOTE: Uninline. main_reset_system(); proto_dude_init("premade\\combat.gcd"); main_load_new(selfrunData.mapFileName); selfrun_playback_loop(&selfrunData); palette_fade_to(white_palette); // NOTE: Uninline. main_unload_new(); // NOTE: Uninline. main_reset_system(); main_menu_create(); } main_selfrun_index++; if (main_selfrun_index >= main_selfrun_count) { main_selfrun_index = 0; } } else { main_menu_hide(true); gmovie_play(MOVIE_INTRO, GAME_MOVIE_PAUSE_MUSIC); } toggle = 1 - toggle; } // 0x48118C static void main_death_scene() { art_flush(); cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_NONE); bool oldCursorIsHidden = mouse_hidden(); if (oldCursorIsHidden) { mouse_show(); } int deathWindowX = 0; int deathWindowY = 0; int win = win_add(deathWindowX, deathWindowY, DEATH_WINDOW_WIDTH, DEATH_WINDOW_HEIGHT, 0, WINDOW_FLAG_0x04); if (win != -1) { do { unsigned char* windowBuffer = win_get_buf(win); if (windowBuffer == NULL) { break; } // DEATH.FRM CacheEntry* backgroundHandle; int fid = art_id(OBJ_TYPE_INTERFACE, 309, 0, 0, 0); unsigned char* background = art_ptr_lock_data(fid, 0, 0, &backgroundHandle); if (background == NULL) { break; } while (mouse_get_buttons() != 0) { get_input(); } kb_clear(); flush_input_buffer(); buf_to_buf(background, 640, 480, 640, windowBuffer, 640); art_ptr_unlock(backgroundHandle); const char* deathFileName = endgameGetDeathEndingFileName(); int subtitles = 0; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitles); if (subtitles != 0) { char text[512]; if (mainDeathGrabTextFile(deathFileName, text) == 0) { debug_printf("\n((ShowDeath)): %s\n", text); short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (mainDeathWordWrap(text, 560, beginnings, &count) == 0) { unsigned char* p = windowBuffer + 640 * (480 - text_height() * count - 8); buf_fill(p - 602, 564, text_height() * count + 2, 640, 0); p += 40; for (int index = 0; index < count; index++) { text_to_buf(p, text + beginnings[index], 560, 640, colorTable[32767]); p += 640 * text_height(); } } } } win_draw(win); loadColorTable("art\\intrface\\death.pal"); palette_fade_to(cmap); main_death_voiceover_done = false; gsound_speech_callback_set(main_death_voiceover_callback); unsigned int delay; if (gsound_speech_play(deathFileName, 10, 14, 15) == -1) { delay = 3000; } else { delay = UINT_MAX; } gsound_speech_play_preloaded(); unsigned int time = get_time(); int keyCode; do { keyCode = get_input(); } while (keyCode == -1 && !main_death_voiceover_done && elapsed_time(time) < delay); gsound_speech_callback_set(NULL); gsound_speech_stop(); while (mouse_get_buttons() != 0) { get_input(); } if (keyCode == -1) { pause_for_tocks(500); } palette_fade_to(black_palette); loadColorTable("color.pal"); } while (0); win_delete(win); } if (oldCursorIsHidden) { mouse_hide(); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); cycle_enable(); } // 0x4814A8 static void main_death_voiceover_callback() { main_death_voiceover_done = true; } // Read endgame subtitle. // // 0x4814B4 static int mainDeathGrabTextFile(const char* fileName, char* dest) { const char* p = strrchr(fileName, '\\'); if (p == NULL) { return -1; } char* language = NULL; if (!config_get_string(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { debug_printf("MAIN: Error grabing language for ending. Defaulting to english.\n"); language = _aEnglish_2; } char path[MAX_PATH]; sprintf(path, "text\\%s\\cuts\\%s%s", language, p + 1, ".TXT"); File* stream = db_fopen(path, "rt"); if (stream == NULL) { return -1; } while (true) { int c = db_fgetc(stream); if (c == -1) { break; } if (c == '\n') { c = ' '; } *dest++ = (c & 0xFF); } db_fclose(stream); *dest = '\0'; return 0; } // 0x481598 static int mainDeathWordWrap(char* text, int width, short* beginnings, short* count) { while (true) { char* sep = strchr(text, ':'); if (sep == NULL) { break; } if (sep - 1 < text) { break; } sep[0] = ' '; sep[-1] = ' '; } if (word_wrap(text, width, beginnings, count) == -1) { return -1; } *count -= 1; for (int index = 1; index < *count; index++) { char* p = text + beginnings[index]; while (p >= text && *p != ' ') { p--; beginnings[index]--; } if (p != NULL) { *p = '\0'; beginnings[index]++; } } return 0; } ================================================ FILE: src/game/main.h ================================================ #ifndef FALLOUT_GAME_MAIN_H_ #define FALLOUT_GAME_MAIN_H_ extern int main_game_paused; int RealMain(int argc, char** argv); #endif /* FALLOUT_GAME_MAIN_H_ */ ================================================ FILE: src/game/mainmenu.c ================================================ #include "game/mainmenu.h" #include #include #include #include "game/art.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gsound.h" #include "game/options.h" #include "game/palette.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #include "plib/gnw/text.h" #include "game/version.h" #define MAIN_MENU_WINDOW_WIDTH 640 #define MAIN_MENU_WINDOW_HEIGHT 480 typedef enum MainMenuButton { MAIN_MENU_BUTTON_INTRO, MAIN_MENU_BUTTON_NEW_GAME, MAIN_MENU_BUTTON_LOAD_GAME, MAIN_MENU_BUTTON_OPTIONS, MAIN_MENU_BUTTON_CREDITS, MAIN_MENU_BUTTON_EXIT, MAIN_MENU_BUTTON_COUNT, } MainMenuButton; static int main_menu_fatal_error(); static void main_menu_play_sound(const char* fileName); // 0x5194F0 static int main_window = -1; // 0x5194F4 static unsigned char* main_window_buf = NULL; // 0x5194F8 static unsigned char* background_data = NULL; // 0x5194FC static unsigned char* button_up_data = NULL; // 0x519500 static unsigned char* button_down_data = NULL; // 0x519504 bool in_main_menu = false; // 0x519508 static bool main_menu_created = false; // 0x51950C static unsigned int main_menu_timeout = 120000; // 0x519510 static int button_values[MAIN_MENU_BUTTON_COUNT] = { KEY_LOWERCASE_I, // intro KEY_LOWERCASE_N, // new game KEY_LOWERCASE_L, // load game KEY_LOWERCASE_O, // options KEY_LOWERCASE_C, // credits KEY_LOWERCASE_E, // exit }; // 0x519528 static int return_values[MAIN_MENU_BUTTON_COUNT] = { MAIN_MENU_INTRO, MAIN_MENU_NEW_GAME, MAIN_MENU_LOAD_GAME, MAIN_MENU_OPTIONS, MAIN_MENU_CREDITS, MAIN_MENU_EXIT, }; // 0x614840 static int buttons[MAIN_MENU_BUTTON_COUNT]; // 0x614858 static bool main_menu_is_hidden; // 0x61485C static CacheEntry* button_up_key; // 0x614860 static CacheEntry* button_down_key; // 0x614864 static CacheEntry* background_key; // 0x481650 int main_menu_create() { int fid; MessageListItem msg; int len; if (main_menu_created) { return 0; } loadColorTable("color.pal"); int mainMenuWindowX = 0; int mainMenuWindowY = 0; main_window = win_add(mainMenuWindowX, mainMenuWindowY, MAIN_MENU_WINDOW_WIDTH, MAIN_MENU_WINDOW_HEIGHT, 0, WINDOW_HIDDEN | WINDOW_FLAG_0x04); if (main_window == -1) { // NOTE: Uninline. return main_menu_fatal_error(); } main_window_buf = win_get_buf(main_window); // mainmenu.frm int backgroundFid = art_id(OBJ_TYPE_INTERFACE, 140, 0, 0, 0); background_data = art_ptr_lock_data(backgroundFid, 0, 0, &background_key); if (background_data == NULL) { // NOTE: Uninline. return main_menu_fatal_error(); } buf_to_buf(background_data, 640, 480, 640, main_window_buf, 640); art_ptr_unlock(background_key); int oldFont = text_curr(); text_font(100); // Copyright. msg.num = 20; if (message_search(&misc_message_file, &msg)) { win_print(main_window, msg.text, 0, 15, 460, colorTable[21091] | 0x6000000); } // Version. char version[VERSION_MAX]; getverstr(version); len = text_width(version); win_print(main_window, version, 0, 615 - len, 460, colorTable[21091] | 0x6000000); // menuup.frm fid = art_id(OBJ_TYPE_INTERFACE, 299, 0, 0, 0); button_up_data = art_ptr_lock_data(fid, 0, 0, &button_up_key); if (button_up_data == NULL) { // NOTE: Uninline. return main_menu_fatal_error(); } // menudown.frm fid = art_id(OBJ_TYPE_INTERFACE, 300, 0, 0, 0); button_down_data = art_ptr_lock_data(fid, 0, 0, &button_down_key); if (button_down_data == NULL) { // NOTE: Uninline. return main_menu_fatal_error(); } for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { buttons[index] = -1; } for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { buttons[index] = win_register_button(main_window, 30, 19 + index * 42 - index, 26, 26, -1, -1, 1111, button_values[index], button_up_data, button_down_data, 0, 32); if (buttons[index] == -1) { // NOTE: Uninline. return main_menu_fatal_error(); } win_register_button_mask(buttons[index], button_up_data); } text_font(104); for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { msg.num = 9 + index; if (message_search(&misc_message_file, &msg)) { len = text_width(msg.text); text_to_buf(main_window_buf + 640 * (42 * index - index + 20) + 126 - (len / 2), msg.text, 640 - (126 - (len / 2)) - 1, 640, colorTable[21091]); } } text_font(oldFont); main_menu_created = true; main_menu_is_hidden = true; return 0; } // 0x481968 void main_menu_destroy() { if (!main_menu_created) { return; } for (int index = 0; index < MAIN_MENU_BUTTON_COUNT; index++) { // FIXME: Why it tries to free only invalid buttons? if (buttons[index] == -1) { win_delete_button(buttons[index]); } } if (button_down_data) { art_ptr_unlock(button_down_key); button_down_key = NULL; button_down_data = NULL; } if (button_up_data) { art_ptr_unlock(button_up_key); button_up_key = NULL; button_up_data = NULL; } if (main_window != -1) { win_delete(main_window); } main_menu_created = false; } // 0x481A00 void main_menu_hide(bool animate) { if (!main_menu_created) { return; } if (main_menu_is_hidden) { return; } soundContinueAll(); if (animate) { palette_fade_to(black_palette); soundContinueAll(); } win_hide(main_window); main_menu_is_hidden = true; } // 0x481A48 void main_menu_show(bool animate) { if (!main_menu_created) { return; } if (!main_menu_is_hidden) { return; } win_show(main_window); if (animate) { loadColorTable("color.pal"); palette_fade_to(cmap); } main_menu_is_hidden = false; } // NOTE: Unused. // // 0x481A8C int main_menu_is_shown() { return main_menu_created ? main_menu_is_hidden == 0 : 0; } // 0x481AA8 int main_menu_is_enabled() { return 1; } // NOTE: Unused. // // 0x481AB0 void main_menu_set_timeout(unsigned int timeout) { main_menu_timeout = 60000 * timeout; } // NOTE: Unused. // // 0x481AD0 unsigned int main_menu_get_timeout() { return main_menu_timeout / 1000 / 60; } // 0x481AEC int main_menu_loop() { in_main_menu = true; bool oldCursorIsHidden = mouse_hidden(); if (oldCursorIsHidden) { mouse_show(); } unsigned int tick = get_time(); int rc = -1; while (rc == -1) { int keyCode = get_input(); for (int buttonIndex = 0; buttonIndex < MAIN_MENU_BUTTON_COUNT; buttonIndex++) { if (keyCode == button_values[buttonIndex] || keyCode == toupper(button_values[buttonIndex])) { // NOTE: Uninline. main_menu_play_sound("nmselec1"); rc = return_values[buttonIndex]; if (buttonIndex == MAIN_MENU_BUTTON_CREDITS && (keys[DIK_RSHIFT] != KEY_STATE_UP || keys[DIK_LSHIFT] != KEY_STATE_UP)) { rc = MAIN_MENU_QUOTES; } break; } } if (rc == -1) { if (keyCode == KEY_CTRL_R) { rc = MAIN_MENU_SELFRUN; continue; } else if (keyCode == KEY_PLUS || keyCode == KEY_EQUAL) { IncGamma(); } else if (keyCode == KEY_MINUS || keyCode == KEY_UNDERSCORE) { DecGamma(); } else if (keyCode == KEY_UPPERCASE_D || keyCode == KEY_LOWERCASE_D) { rc = MAIN_MENU_SCREENSAVER; continue; } else if (keyCode == 1111) { if (!(mouse_get_buttons() & MOUSE_EVENT_LEFT_BUTTON_REPEAT)) { // NOTE: Uninline. main_menu_play_sound("nmselec0"); } continue; } } if (keyCode == KEY_ESCAPE || game_user_wants_to_quit == 3) { rc = MAIN_MENU_EXIT; // NOTE: Uninline. main_menu_play_sound("nmselec1"); break; } else if (game_user_wants_to_quit == 2) { game_user_wants_to_quit = 0; } else { if (elapsed_time(tick) >= main_menu_timeout) { rc = MAIN_MENU_TIMEOUT; } } } if (oldCursorIsHidden) { mouse_hide(); } in_main_menu = false; return rc; } // NOTE: Inlined. // // 0x481C88 static int main_menu_fatal_error() { main_menu_destroy(); return -1; } // NOTE: Inlined. // // 0x481C94 static void main_menu_play_sound(const char* fileName) { gsound_play_sfx_file(fileName); } ================================================ FILE: src/game/mainmenu.h ================================================ #ifndef FALLOUT_GAME_MAINMENU_H_ #define FALLOUT_GAME_MAINMENU_H_ #include typedef enum MainMenuOption { MAIN_MENU_INTRO, MAIN_MENU_NEW_GAME, MAIN_MENU_LOAD_GAME, MAIN_MENU_SCREENSAVER, MAIN_MENU_TIMEOUT, MAIN_MENU_CREDITS, MAIN_MENU_QUOTES, MAIN_MENU_EXIT, MAIN_MENU_SELFRUN, MAIN_MENU_OPTIONS, } MainMenuOption; extern bool in_main_menu; int main_menu_create(); void main_menu_destroy(); void main_menu_hide(bool animate); void main_menu_show(bool animate); int main_menu_is_shown(); int main_menu_is_enabled(); void main_menu_set_timeout(unsigned int timeout); unsigned int main_menu_get_timeout(); int main_menu_loop(); #endif /* FALLOUT_GAME_MAINMENU_H_ */ ================================================ FILE: src/game/map.c ================================================ #include "game/map.h" #include #include #include #include "game/anim.h" #include "game/automap.h" #include "game/editor.h" #include "plib/color/color.h" #include "game/combat.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/light.h" #include "game/loadsave.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/palette.h" #include "game/pipboy.h" #include "game/proto.h" #include "game/protinst.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/textobj.h" #include "game/tile.h" #include "plib/gnw/gnw.h" #include "plib/gnw/intrface.h" #include "plib/gnw/svga.h" #include "game/worldmap.h" static void map_display_draw(Rect* rect); static void map_scroll_refresh_game(Rect* rect); static void map_scroll_refresh_mapper(Rect* rect); static int map_allocate_global_vars(int count); static void map_free_global_vars(); static int map_load_global_vars(File* stream); static int map_allocate_local_vars(int count); static void map_free_local_vars(); static int map_load_local_vars(File* stream); static void map_place_dude_and_mouse(); static void square_init(); static void square_reset(); static int square_load(File* stream, int a2); static int map_write_MapData(MapHeader* ptr, File* stream); static int map_read_MapData(MapHeader* ptr, File* stream); // 0x50B058 char byte_50B058[] = ""; // 0x50B30C char _aErrorF2[] = "ERROR! F2"; // 0x519540 static IsoWindowRefreshProc* map_scroll_refresh = map_scroll_refresh_game; // 0x519544 static int map_data_elev_flags[ELEVATION_COUNT] = { 2, 4, 8, }; // 0x519550 static unsigned int map_last_scroll_time = 0; // 0x519554 static bool map_bk_enabled = false; // 0x519558 static int mapEntranceElevation = 0; // 0x51955C static int mapEntranceTileNum = -1; // 0x519560 static int mapEntranceRotation = ROTATION_NE; // 0x519564 int map_script_id = -1; // local_vars // 0x519568 int* map_local_vars = NULL; // map_vars // 0x51956C int* map_global_vars = NULL; // local_vars_num // 0x519570 int num_map_local_vars = 0; // map_vars_num // 0x519574 int num_map_global_vars = 0; // Current elevation. // // 0x519578 int map_elevation = 0; // 0x519584 static int wmMapIdx = -1; // 0x614868 TileData square_data[ELEVATION_COUNT]; // 0x631D28 static MapTransition map_state; // 0x631D38 static Rect map_display_rect; // map.msg // // map_msg_file // 0x631D48 MessageList map_msg_file; // 0x631D50 static unsigned char* display_buf; // 0x631D54 MapHeader map_data; // 0x631E40 TileData* square[ELEVATION_COUNT]; // 0x631E4C int display_win; // iso_init // 0x481CA0 int iso_init() { tile_disable_scroll_limiting(); tile_disable_scroll_blocking(); // NOTE: Uninline. square_init(); display_win = win_add(0, 0, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, 256, 10); if (display_win == -1) { debug_printf("win_add failed in iso_init\n"); return -1; } display_buf = win_get_buf(display_win); if (display_buf == NULL) { debug_printf("win_get_buf failed in iso_init\n"); return -1; } if (win_get_rect(display_win, &map_display_rect) != 0) { debug_printf("win_get_rect failed in iso_init\n"); return -1; } if (art_init() != 0) { debug_printf("art_init failed in iso_init\n"); return -1; } debug_printf(">art_init\t\t"); if (tile_init(square, SQUARE_GRID_WIDTH, SQUARE_GRID_HEIGHT, HEX_GRID_WIDTH, HEX_GRID_HEIGHT, display_buf, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, scr_size.lrx - scr_size.ulx + 1, map_display_draw) != 0) { debug_printf("tile_init failed in iso_init\n"); return -1; } debug_printf(">tile_init\t\t"); if (obj_init(display_buf, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, scr_size.lrx - scr_size.ulx + 1) != 0) { debug_printf("obj_init failed in iso_init\n"); return -1; } debug_printf(">obj_init\t\t"); cycle_init(); debug_printf(">cycle_init\t\t"); tile_enable_scroll_blocking(); tile_enable_scroll_limiting(); if (intface_init() != 0) { debug_printf("intface_init failed in iso_init\n"); return -1; } debug_printf(">intface_init\t\t"); map_setup_paths(); mapEntranceElevation = -1; mapEntranceTileNum = -1; mapEntranceRotation = -1; return 0; } // 0x481ED4 void iso_reset() { if (map_global_vars != NULL) { mem_free(map_global_vars); map_global_vars = NULL; num_map_global_vars = 0; } if (map_local_vars != NULL) { mem_free(map_local_vars); map_local_vars = NULL; num_map_local_vars = 0; } art_reset(); tile_reset(); obj_reset(); cycle_reset(); intface_reset(); mapEntranceElevation = -1; mapEntranceTileNum = -1; mapEntranceRotation = -1; } // 0x481F48 void iso_exit() { intface_exit(); cycle_exit(); obj_exit(); tile_exit(); art_exit(); if (map_global_vars != NULL) { mem_free(map_global_vars); map_global_vars = NULL; num_map_global_vars = 0; } if (map_local_vars != NULL) { mem_free(map_local_vars); map_local_vars = NULL; num_map_local_vars = 0; } } // 0x481FB4 void map_init() { char* executable; config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, "executable", &executable); if (stricmp(executable, "mapper") == 0) { map_scroll_refresh = map_scroll_refresh_mapper; } if (message_init(&map_msg_file)) { char path[FILENAME_MAX]; sprintf(path, "%smap.msg", msg_path); if (!message_load(&map_msg_file, path)) { debug_printf("\nError loading map_msg_file!"); } } else { debug_printf("\nError initing map_msg_file!"); } // NOTE: Uninline. map_reset(); } // NOTE: Inlined. // // 0x482064 void map_reset() { map_new_map(); add_bk_process(gmouse_bk_process); gmouse_disable(0); win_show(display_win); } // 0x482084 void map_exit() { win_hide(display_win); gmouse_set_cursor(MOUSE_CURSOR_ARROW); remove_bk_process(gmouse_bk_process); if (!message_exit(&map_msg_file)) { debug_printf("\nError exiting map_msg_file!"); } } // 0x4820C0 void map_enable_bk_processes() { if (!map_bk_enabled) { text_object_enable(); if (!game_ui_is_disabled()) { gmouse_enable(); } add_bk_process(object_animate); add_bk_process(dude_fidget); scr_enable_critters(); map_bk_enabled = true; } } // 0x482104 bool map_disable_bk_processes() { if (!map_bk_enabled) { return false; } scr_disable_critters(); remove_bk_process(dude_fidget); remove_bk_process(object_animate); gmouse_disable(0); text_object_disable(); map_bk_enabled = false; return true; } // 0x482148 bool map_bk_processes_are_disabled() { return map_bk_enabled == false; } // map_set_elevation // 0x482158 int map_set_elevation(int elevation) { if (!elevationIsValid(elevation)) { return -1; } bool gameMouseWasVisible = false; if (gmouse_get_cursor() != MOUSE_CURSOR_WAIT_PLANET) { gameMouseWasVisible = gmouse_3d_is_on(); gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_NONE); } if (elevation != map_elevation) { wmMapMarkMapEntranceState(map_data.field_34, elevation, 1); } map_elevation = elevation; register_clear(obj_dude); dude_stand(obj_dude, obj_dude->rotation, obj_dude->fid); partyMemberSyncPosition(); if (map_script_id != -1) { scr_exec_map_update_scripts(); } if (gameMouseWasVisible) { gmouse_3d_on(); } return 0; } // NOTE: Unused. // // 0x4821F4 bool map_is_elevation_empty(int elevation) { return elevation < 0 || elevation >= ELEVATION_COUNT || (map_data.flags & map_data_elev_flags[elevation]) != 0; } // 0x482220 int map_set_global_var(int var, int value) { if (var < 0 || var >= num_map_global_vars) { debug_printf("ERROR: attempt to reference map var out of range: %d", var); return -1; } map_global_vars[var] = value; return 0; } // 0x482250 int map_get_global_var(int var) { if (var < 0 || var >= num_map_global_vars) { debug_printf("ERROR: attempt to reference map var out of range: %d", var); return 0; } return map_global_vars[var]; } // 0x482280 int map_set_local_var(int var, int value) { if (var < 0 || var >= num_map_local_vars) { debug_printf("ERROR: attempt to reference local var out of range: %d", var); return -1; } map_local_vars[var] = value; return 0; } // 0x4822B0 int map_get_local_var(int var) { if (var < 0 || var >= num_map_local_vars) { debug_printf("ERROR: attempt to reference local var out of range: %d", var); return 0; } return map_local_vars[var]; } // Make a room to store more local variables. // // 0x4822E0 int map_malloc_local_var(int a1) { int oldMapLocalVarsLength = num_map_local_vars; num_map_local_vars += a1; int* vars = (int*)mem_realloc(map_local_vars, sizeof(*vars) * num_map_local_vars); if (vars == NULL) { debug_printf("\nError: Ran out of memory!"); } map_local_vars = vars; memset((unsigned char*)vars + sizeof(*vars) * oldMapLocalVarsLength, 0, sizeof(*vars) * a1); return oldMapLocalVarsLength; } // 0x48234C void map_set_entrance_hex(int tile, int elevation, int rotation) { map_data.enteringTile = tile; map_data.enteringElevation = elevation; map_data.enteringRotation = rotation; } // NOTE: Unused. // // 0x48247C void map_set_name(const char* name) { strcpy(map_data.name, name); } // NOTE: Unused. // // 0x4824A4 void map_get_name(char* name) { strcpy(name, map_data.name); } // 0x4824CC char* map_get_elev_idx(int map, int elevation) { if (map < 0 || map >= wmMapMaxCount()) { return NULL; } if (!elevationIsValid(elevation)) { return NULL; } MessageListItem messageListItem; return getmsg(&map_msg_file, &messageListItem, map * 3 + elevation + 200); } // TODO: Check, probably returns true if map1 and map2 represents the same city. // // 0x482528 bool is_map_idx_same(int map1, int map2) { if (map1 < 0 || map1 >= wmMapMaxCount()) { return 0; } if (map2 < 0 || map2 >= wmMapMaxCount()) { return 0; } if (!wmMapIdxIsSaveable(map1)) { return 0; } if (!wmMapIdxIsSaveable(map2)) { return 0; } int city1; if (wmMatchAreaContainingMapIdx(map1, &city1) == -1) { return 0; } int city2; if (wmMatchAreaContainingMapIdx(map2, &city2) == -1) { return 0; } return city1 == city2; } // 0x4825CC int get_map_idx_same(int map1, int map2) { int city1 = -1; if (wmMatchAreaContainingMapIdx(map1, &city1) == -1) { return -1; } int city2 = -2; if (wmMatchAreaContainingMapIdx(map2, &city2) == -1) { return -1; } if (city1 != city2) { return -1; } return city1; } // 0x48261C char* map_get_short_name(int map) { int city; if (wmMatchAreaContainingMapIdx(map, &city) == -1) { return _aErrorF2; } MessageListItem messageListItem; char* name = getmsg(&map_msg_file, &messageListItem, 1500 + city); return name; } // NOTE: Unused. // // 0x482684 char* map_get_description() { return map_get_description_idx(map_data.field_34); } // 0x48268C char* map_get_description_idx(int map) { // 0x631E50 static char scratchStr[40]; // 0x51957C static char* errMapName = byte_50B058; int city; if (wmMatchAreaContainingMapIdx(map, &city) == 0) { wmGetAreaIdxName(city, scratchStr); } else { strcpy(scratchStr, errMapName); } return scratchStr; } // 0x4826B8 int map_get_index_number() { return map_data.field_34; } // 0x4826C0 int map_scroll(int dx, int dy) { if (elapsed_time(map_last_scroll_time) < 33) { return -2; } map_last_scroll_time = get_time(); int screenDx = dx * 32; int screenDy = dy * 24; if (screenDx == 0 && screenDy == 0) { return -1; } gmouse_3d_off(); int centerScreenX; int centerScreenY; tile_coord(tile_center_tile, ¢erScreenX, ¢erScreenY, map_elevation); centerScreenX += screenDx + 16; centerScreenY += screenDy + 8; int newCenterTile = tile_num(centerScreenX, centerScreenY, map_elevation); if (newCenterTile == -1) { return -1; } if (tile_set_center(newCenterTile, 0) == -1) { return -1; } Rect r1; rectCopy(&r1, &map_display_rect); Rect r2; rectCopy(&r2, &r1); int width = scr_size.lrx - scr_size.ulx + 1; int pitch = width; int height = scr_size.lry - scr_size.uly - 99; if (screenDx != 0) { width -= 32; } if (screenDy != 0) { height -= 24; } if (screenDx < 0) { r2.lrx = r2.ulx - screenDx; } else { r2.ulx = r2.lrx - screenDx; } unsigned char* src; unsigned char* dest; int step; if (screenDy < 0) { r1.lry = r1.uly - screenDy; src = display_buf + pitch * (height - 1); dest = display_buf + pitch * (scr_size.lry - scr_size.uly - 100); if (screenDx < 0) { dest -= screenDx; } else { src += screenDx; } step = -pitch; } else { r1.uly = r1.lry - screenDy; dest = display_buf; src = display_buf + pitch * screenDy; if (screenDx < 0) { dest -= screenDx; } else { src += screenDx; } step = pitch; } for (int y = 0; y < height; y++) { memmove(dest, src, width); dest += step; src += step; } if (screenDx != 0) { map_scroll_refresh(&r2); } if (screenDy != 0) { map_scroll_refresh(&r1); } win_draw(display_win); return 0; } // 0x482900 char* map_file_path(char* name) { // 0x631E78 static char map_path[MAX_PATH]; if (*name != '\\') { sprintf(map_path, "maps\\%s", name); return map_path; } return name; } // 0x482924 int mapSetEntranceInfo(int elevation, int tile_num, int orientation) { mapEntranceElevation = elevation; mapEntranceTileNum = tile_num; mapEntranceRotation = orientation; return 0; } // 0x482938 void map_new_map() { map_set_elevation(0); tile_set_center(20100, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS); memset(&map_state, 0, sizeof(map_state)); map_data.enteringElevation = 0; map_data.enteringRotation = 0; map_data.localVariablesCount = 0; map_data.version = 20; map_data.name[0] = '\0'; map_data.enteringTile = 20100; obj_remove_all(); anim_stop(); if (map_global_vars != NULL) { mem_free(map_global_vars); map_global_vars = NULL; num_map_global_vars = 0; } if (map_local_vars != NULL) { mem_free(map_local_vars); map_local_vars = NULL; num_map_local_vars = 0; } square_reset(); map_place_dude_and_mouse(); tile_refresh_display(); } // 0x482A68 int map_load(char* fileName) { int rc; strupr(fileName); rc = -1; char* extension = strstr(fileName, ".MAP"); if (extension != NULL) { strcpy(extension, ".SAV"); const char* filePath = map_file_path(fileName); File* stream = db_fopen(filePath, "rb"); strcpy(extension, ".MAP"); if (stream != NULL) { db_fclose(stream); rc = map_load_in_game(fileName); wmMapMusicStart(); } } if (rc == -1) { const char* filePath = map_file_path(fileName); File* stream = db_fopen(filePath, "rb"); if (stream != NULL) { rc = map_load_file(stream); db_fclose(stream); } if (rc == 0) { strcpy(map_data.name, fileName); obj_dude->data.critter.combat.whoHitMe = NULL; } } return rc; } // 0x482B34 int map_load_idx(int map) { scr_set_ext_param(map_script_id, map); char name[16]; if (wmMapIdxToName(map, name) == -1) { return -1; } wmMapIdx = map; int rc = map_load(name); wmMapMusicStart(); return rc; } // 0x482B74 int map_load_file(File* stream) { map_save_in_game(true); gsound_background_play("wind2", 12, 13, 16); map_disable_bk_processes(); partyMemberPrepLoad(); gmouse_disable_scrolling(); int savedMouseCursorId = gmouse_get_cursor(); gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET); db_register_callback(gmouse_bk_process, 32768); tile_disable_refresh(); int rc = 0; win_fill(display_win, 0, 0, scr_size.lrx - scr_size.ulx + 1, scr_size.lry - scr_size.uly - 99, colorTable[0]); win_draw(display_win); anim_stop(); scr_disable(); map_script_id = -1; const char* error = NULL; error = "Invalid file handle"; if (stream == NULL) { goto err; } error = "Error reading header"; if (map_read_MapData(&map_data, stream) != 0) { goto err; } error = "Invalid map version"; if (map_data.version != 19 && map_data.version != 20) { goto err; } if (mapEntranceElevation == -1) { mapEntranceElevation = map_data.enteringElevation; mapEntranceTileNum = map_data.enteringTile; mapEntranceRotation = map_data.enteringRotation; } obj_remove_all(); if (map_data.globalVariablesCount < 0) { map_data.globalVariablesCount = 0; } if (map_data.localVariablesCount < 0) { map_data.localVariablesCount = 0; } error = "Error allocating global vars"; // NOTE: Uninline. if (map_allocate_global_vars(map_data.globalVariablesCount) != 0) { goto err; } error = "Error loading global vars"; // NOTE: Uninline. if (map_load_global_vars(stream) != 0) { goto err; } error = "Error allocating local vars"; // NOTE: Uninline. if (map_allocate_local_vars(map_data.localVariablesCount) != 0) { goto err; } error = "Error loading local vars"; if (map_load_local_vars(stream) != 0) { goto err; } if (square_load(stream, map_data.flags) != 0) { goto err; } error = "Error reading scripts"; if (scr_load(stream) != 0) { goto err; } error = "Error reading objects"; if (obj_load(stream) != 0) { goto err; } if ((map_data.flags & 1) == 0) { map_fix_critter_combat_data(); } error = "Error setting map elevation"; if (map_set_elevation(mapEntranceElevation) != 0) { goto err; } error = "Error setting tile center"; if (tile_set_center(mapEntranceTileNum, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) { goto err; } light_set_ambient(LIGHT_LEVEL_MAX, false); obj_move_to_tile(obj_dude, tile_center_tile, map_elevation, NULL); obj_set_rotation(obj_dude, mapEntranceRotation, NULL); map_data.field_34 = wmMapMatchNameToIdx(map_data.name); if ((map_data.flags & 1) == 0) { char path[MAX_PATH]; sprintf(path, "maps\\%s", map_data.name); char* extension = strstr(path, ".MAP"); if (extension == NULL) { extension = strstr(path, ".map"); } if (extension != NULL) { *extension = '\0'; } strcat(path, ".GAM"); game_load_info_vars(path, "MAP_GLOBAL_VARS:", &num_map_global_vars, &map_global_vars); map_data.globalVariablesCount = num_map_global_vars; } scr_enable(); if (map_data.scriptIndex > 0) { error = "Error creating new map script"; if (scr_new(&map_script_id, SCRIPT_TYPE_SYSTEM) == -1) { goto err; } Object* object; int fid = art_id(OBJ_TYPE_MISC, 12, 0, 0, 0); obj_new(&object, fid, -1); object->flags |= (OBJECT_LIGHT_THRU | OBJECT_TEMPORARY | OBJECT_HIDDEN); obj_move_to_tile(object, 1, 0, NULL); object->sid = map_script_id; scr_set_ext_param(map_script_id, (map_data.flags & 1) == 0); Script* script; scr_ptr(map_script_id, &script); script->field_14 = map_data.scriptIndex - 1; script->flags |= SCRIPT_FLAG_0x08; object->id = new_obj_id(); script->field_1C = object->id; script->owner = object; scr_spatials_disable(); exec_script_proc(map_script_id, SCRIPT_PROC_MAP_ENTER); scr_spatials_enable(); error = "Error Setting up random encounter"; if (wmSetupRandomEncounter() == -1) { goto err; } } error = NULL; err: if (error != NULL) { char message[100]; // TODO: Size is probably wrong. sprintf(message, "%s while loading map.", error); debug_printf(message); map_new_map(); rc = -1; } else { obj_preload_art_cache(map_data.flags); } partyMemberRecoverLoad(); intface_show(); proto_dude_update_gender(); map_place_dude_and_mouse(); db_register_callback(NULL, 0); map_enable_bk_processes(); gmouse_disable_scrolling(); gmouse_set_cursor(MOUSE_CURSOR_WAIT_PLANET); if (scr_load_all_scripts() == -1) { debug_printf("\n Error: scr_load_all_scripts failed!"); } scr_exec_map_enter_scripts(); scr_exec_map_update_scripts(); tile_enable_refresh(); if (map_state.map > 0) { if (map_state.rotation >= 0) { obj_set_rotation(obj_dude, map_state.rotation, NULL); } } else { tile_refresh_display(); } gtime_q_add(); if (gsound_sfx_q_start() == -1) { rc = -1; } wmMapMarkVisited(map_data.field_34); wmMapMarkMapEntranceState(map_data.field_34, map_elevation, 1); if (wmCheckGameAreaEvents() != 0) { rc = -1; } db_register_callback(NULL, 0); if (game_ui_is_disabled() == 0) { gmouse_enable_scrolling(); } gmouse_set_cursor(savedMouseCursorId); mapEntranceElevation = -1; mapEntranceTileNum = -1; mapEntranceRotation = -1; gmPaletteFinish(); map_data.version = 20; return rc; } // 0x483188 int map_load_in_game(char* fileName) { debug_printf("\nMAP: Loading SAVED map."); char mapName[16]; // TODO: Size is probably wrong. strmfe(mapName, fileName, "SAV"); int rc = map_load(mapName); if (game_time() >= map_data.lastVisitTime) { if (((game_time() - map_data.lastVisitTime) / GAME_TIME_TICKS_PER_HOUR) >= 24) { obj_unjam_all_locks(); } if (map_age_dead_critters() == -1) { debug_printf("\nError: Critter aging failed on map load!"); return -1; } } if (!wmMapIsSaveable()) { debug_printf("\nDestroying RANDOM encounter map."); char v15[16]; strcpy(v15, map_data.name); strmfe(map_data.name, v15, "SAV"); MapDirEraseFile("MAPS\\", map_data.name); strcpy(map_data.name, v15); } return rc; } // 0x48328C int map_age_dead_critters() { if (!wmMapDeadBodiesAge()) { return 0; } int hoursSinceLastVisit = (game_time() - map_data.lastVisitTime) / GAME_TIME_TICKS_PER_HOUR; if (hoursSinceLastVisit == 0) { return 0; } Object* obj = obj_find_first(); while (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER && obj != obj_dude && !isPartyMember(obj) && !critter_is_dead(obj)) { obj->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; if (critterGetKillType(obj) != KILL_TYPE_ROBOT && critter_flag_check(obj->pid, CRITTER_NO_HEAL) == 0) { critter_heal_hours(obj, hoursSinceLastVisit); } } obj = obj_find_next(); } int agingType; if (hoursSinceLastVisit > 6 * 24) { agingType = 1; } else if (hoursSinceLastVisit > 14 * 24) { agingType = 2; } else { return 0; } int capacity = 100; int count = 0; Object** objects = (Object**)mem_malloc(sizeof(*objects) * capacity); obj = obj_find_first(); while (obj != NULL) { int type = PID_TYPE(obj->pid); if (type == OBJ_TYPE_CRITTER) { if (obj != obj_dude && critter_is_dead(obj)) { if (critterGetKillType(obj) != KILL_TYPE_ROBOT && critter_flag_check(obj->pid, CRITTER_NO_HEAL) == 0) { objects[count++] = obj; if (count >= capacity) { capacity *= 2; objects = (Object**)mem_realloc(objects, sizeof(*objects) * capacity); if (objects == NULL) { debug_printf("\nError: Out of Memory!"); return -1; } } } } } else if (agingType == 2 && type == OBJ_TYPE_MISC && obj->pid == 0x500000B) { objects[count++] = obj; if (count >= capacity) { capacity *= 2; objects = (Object**)mem_realloc(objects, sizeof(*objects) * capacity); if (objects == NULL) { debug_printf("\nError: Out of Memory!"); return -1; } } } obj = obj_find_next(); } int rc = 0; for (int index = 0; index < count; index++) { Object* obj = objects[index]; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { if (critter_flag_check(obj->pid, CRITTER_NO_DROP) == 0) { item_drop_all(obj, obj->tile); } Object* blood; if (obj_pid_new(&blood, 0x5000004) == -1) { rc = -1; break; } obj_move_to_tile(blood, obj->tile, obj->elevation, NULL); Proto* proto; proto_ptr(obj->pid, &proto); int frame = roll_random(0, 3); if ((proto->critter.flags & 0x800)) { frame += 6; } else { if (critterGetKillType(obj) != KILL_TYPE_RAT && critterGetKillType(obj) != KILL_TYPE_MANTIS) { frame += 3; } } obj_set_frame(blood, frame, NULL); } register_clear(obj); obj_erase_object(obj, NULL); } mem_free(objects); return rc; } // 0x48358C int map_target_load_area() { int city = -1; if (wmMatchAreaContainingMapIdx(map_data.field_34, &city) == -1) { city = -1; } return city; } // 0x4835B4 int map_leave_map(MapTransition* transition) { if (transition == NULL) { return -1; } memcpy(&map_state, transition, sizeof(map_state)); if (map_state.map == 0) { map_state.map = -2; } if (isInCombat()) { game_user_wants_to_quit = 1; } return 0; } // 0x4835F8 int map_check_state() { if (map_state.map == 0) { return 0; } gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_NONE); if (map_state.map == -1) { if (!isInCombat()) { anim_stop(); wmTownMap(); memset(&map_state, 0, sizeof(map_state)); } } else if (map_state.map == -2) { if (!isInCombat()) { anim_stop(); wmWorldMap(); memset(&map_state, 0, sizeof(map_state)); } } else { if (!isInCombat()) { if (map_state.map != map_data.field_34 || map_elevation == map_state.elevation) { map_load_idx(map_state.map); } if (map_state.tile != -1 && map_state.tile != 0 && map_data.field_34 != MAP_MODOC_BEDNBREAKFAST && map_data.field_34 != MAP_THE_SQUAT_A && elevationIsValid(map_state.elevation)) { obj_move_to_tile(obj_dude, map_state.tile, map_state.elevation, NULL); map_set_elevation(map_state.elevation); obj_set_rotation(obj_dude, map_state.rotation, NULL); } if (tile_set_center(obj_dude->tile, TILE_SET_CENTER_REFRESH_WINDOW) == -1) { debug_printf("\nError: map: attempt to center out-of-bounds!"); } memset(&map_state, 0, sizeof(map_state)); int city; wmMatchAreaContainingMapIdx(map_data.field_34, &city); if (wmTeleportToArea(city) == -1) { debug_printf("\nError: couldn't make jump on worldmap for map jump!"); } } } return 0; } // 0x483784 void map_fix_critter_combat_data() { for (Object* object = obj_find_first(); object != NULL; object = obj_find_next()) { if (object->pid == -1) { continue; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { continue; } if (object->data.critter.combat.whoHitMeCid == -1) { object->data.critter.combat.whoHitMe = NULL; } } } // 0x483850 int map_save() { char temp[80]; temp[0] = '\0'; char* masterPatchesPath; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) { strcat(temp, masterPatchesPath); mkdir(temp); strcat(temp, "\\MAPS"); mkdir(temp); } int rc = -1; if (map_data.name[0] != '\0') { char* mapFileName = map_file_path(map_data.name); File* stream = db_fopen(mapFileName, "wb"); if (stream != NULL) { rc = map_save_file(stream); db_fclose(stream); } else { sprintf(temp, "Unable to open %s to write!", map_data.name); debug_printf(temp); } if (rc == 0) { sprintf(temp, "%s saved.", map_data.name); debug_printf(temp); } } else { debug_printf("\nError: map_save: map header corrupt!"); } return rc; } // 0x483980 int map_save_file(File* stream) { if (stream == NULL) { return -1; } scr_disable(); for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { int tile; for (tile = 0; tile < SQUARE_GRID_SIZE; tile++) { int fid; fid = art_id(OBJ_TYPE_TILE, square[elevation]->field_0[tile] & 0xFFF, 0, 0, 0); if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { break; } fid = art_id(OBJ_TYPE_TILE, (square[elevation]->field_0[tile] >> 16) & 0xFFF, 0, 0, 0); if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { break; } } if (tile == SQUARE_GRID_SIZE) { Object* object = obj_find_first_at(elevation); if (object != NULL) { // TODO: Implementation is slightly different, check in debugger. while (object != NULL && (object->flags & OBJECT_TEMPORARY)) { object = obj_find_next_at(); } if (object != NULL) { map_data.flags &= ~map_data_elev_flags[elevation]; } else { map_data.flags |= map_data_elev_flags[elevation]; } } else { map_data.flags |= map_data_elev_flags[elevation]; } } else { map_data.flags &= ~map_data_elev_flags[elevation]; } } map_data.localVariablesCount = num_map_local_vars; map_data.globalVariablesCount = num_map_global_vars; map_data.darkness = 1; map_write_MapData(&map_data, stream); if (map_data.globalVariablesCount != 0) { db_fwriteIntCount(stream, map_global_vars, map_data.globalVariablesCount); } if (map_data.localVariablesCount != 0) { db_fwriteIntCount(stream, map_local_vars, map_data.localVariablesCount); } for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { if ((map_data.flags & map_data_elev_flags[elevation]) == 0) { db_fwriteLongCount(stream, square[elevation]->field_0, SQUARE_GRID_SIZE); } } char err[80]; if (scr_save(stream) == -1) { sprintf(err, "Error saving scripts in %s", map_data.name); win_msg(err, 80, 80, colorTable[31744]); } if (obj_save(stream) == -1) { sprintf(err, "Error saving objects in %s", map_data.name); win_msg(err, 80, 80, colorTable[31744]); } scr_enable(); return 0; } // 0x483C98 int map_save_in_game(bool a1) { if (map_data.name[0] == '\0') { return 0; } anim_stop(); partyMemberSaveProtos(); if (a1) { queue_leaving_map(); partyMemberPrepLoad(); partyMemberPrepItemSaveAll(); scr_exec_map_exit_scripts(); if (map_script_id != -1) { Script* script; scr_ptr(map_script_id, &script); } gtime_q_add(); obj_reset_roof(); } map_data.flags |= 0x01; map_data.lastVisitTime = game_time(); char name[16]; if (a1 && !wmMapIsSaveable()) { debug_printf("\nNot saving RANDOM encounter map."); strcpy(name, map_data.name); strmfe(map_data.name, name, "SAV"); MapDirEraseFile("MAPS\\", map_data.name); strcpy(map_data.name, name); } else { debug_printf("\n Saving \".SAV\" map."); strcpy(name, map_data.name); strmfe(map_data.name, name, "SAV"); if (map_save() == -1) { return -1; } strcpy(map_data.name, name); automap_pip_save(); if (a1) { map_data.name[0] = '\0'; obj_remove_all(); proto_remove_all(); square_reset(); gtime_q_add(); } } return 0; } // 0x483E28 void map_setup_paths() { char path[FILENAME_MAX]; char* masterPatchesPath; if (config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatchesPath)) { strcpy(path, masterPatchesPath); } else { strcpy(path, "DATA"); } mkdir(path); strcat(path, "\\MAPS"); mkdir(path); } // 0x483ED0 static void map_display_draw(Rect* rect) { win_draw_rect(display_win, rect); } // 0x483EE4 static void map_scroll_refresh_game(Rect* rect) { Rect clampedDirtyRect; if (rect_inside_bound(rect, &map_display_rect, &clampedDirtyRect) == -1) { return; } square_render_floor(&clampedDirtyRect, map_elevation); grid_render(&clampedDirtyRect, map_elevation); obj_render_pre_roof(&clampedDirtyRect, map_elevation); square_render_roof(&clampedDirtyRect, map_elevation); obj_render_post_roof(&clampedDirtyRect, map_elevation); } // 0x483F44 static void map_scroll_refresh_mapper(Rect* rect) { Rect clampedDirtyRect; if (rect_inside_bound(rect, &map_display_rect, &clampedDirtyRect) == -1) { return; } buf_fill(display_buf + clampedDirtyRect.uly * (scr_size.lrx - scr_size.ulx + 1) + clampedDirtyRect.ulx, clampedDirtyRect.lrx - clampedDirtyRect.ulx + 1, clampedDirtyRect.lry - clampedDirtyRect.uly + 1, scr_size.lrx - scr_size.ulx + 1, 0); square_render_floor(&clampedDirtyRect, map_elevation); grid_render(&clampedDirtyRect, map_elevation); obj_render_pre_roof(&clampedDirtyRect, map_elevation); square_render_roof(&clampedDirtyRect, map_elevation); obj_render_post_roof(&clampedDirtyRect, map_elevation); } // NOTE: Inlined. // // 0x483FE4 static int map_allocate_global_vars(int count) { map_free_global_vars(); if (count != 0) { map_global_vars = (int*)mem_malloc(sizeof(*map_global_vars) * count); if (map_global_vars == NULL) { return -1; } } num_map_global_vars = count; return 0; } // 0x484038 static void map_free_global_vars() { if (map_global_vars != NULL) { mem_free(map_global_vars); map_global_vars = NULL; num_map_global_vars = 0; } } // NOTE: Inlined. // // 0x48405C static int map_load_global_vars(File* stream) { if (db_freadIntCount(stream, map_global_vars, num_map_global_vars) != 0) { return -1; } return 0; } // NOTE: Inlined. // // 0x484080 static int map_allocate_local_vars(int count) { map_free_local_vars(); if (count != 0) { map_local_vars = (int*)mem_malloc(sizeof(*map_local_vars) * count); if (map_local_vars == NULL) { return -1; } } num_map_local_vars = count; return 0; } // 0x4840D4 static void map_free_local_vars() { if (map_local_vars != NULL) { mem_free(map_local_vars); map_local_vars = NULL; num_map_local_vars = 0; } } // NOTE: Inlined. // // 0x4840F8 static int map_load_local_vars(File* stream) { if (db_freadIntCount(stream, map_local_vars, num_map_local_vars) != 0) { return -1; } return 0; } // 0x48411C static void map_place_dude_and_mouse() { obj_clear_seen(); if (obj_dude != NULL) { if (FID_ANIM_TYPE(obj_dude->fid) != ANIM_STAND) { obj_set_frame(obj_dude, 0, 0); obj_dude->fid = art_id(OBJ_TYPE_CRITTER, obj_dude->fid & 0xFFF, ANIM_STAND, (obj_dude->fid & 0xF000) >> 12, obj_dude->rotation + 1); } if (obj_dude->tile == -1) { obj_move_to_tile(obj_dude, tile_center_tile, map_elevation, NULL); obj_set_rotation(obj_dude, map_data.enteringRotation, 0); } obj_set_light(obj_dude, 4, 0x10000, 0); obj_dude->flags |= OBJECT_TEMPORARY; dude_stand(obj_dude, obj_dude->rotation, obj_dude->fid); partyMemberSyncPosition(); } gmouse_3d_reset_fid(); gmouse_3d_on(); } // NOTE: Inlined. // // 0x4841F0 static void square_init() { int elevation; for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) { square[elevation] = &(square_data[elevation]); } } // 0x484210 static void square_reset() { for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { int* p = square[elevation]->field_0; for (int y = 0; y < SQUARE_GRID_HEIGHT; y++) { for (int x = 0; x < SQUARE_GRID_WIDTH; x++) { // TODO: Strange math, initially right, but need to figure it out and // check subsequent calls. int fid = *p; fid &= ~0xFFFF; *p = (((art_id(OBJ_TYPE_TILE, 1, 0, 0, 0) & 0xFFF) | (((fid >> 16) & 0xF000) >> 12)) << 16) | (fid & 0xFFFF); fid = *p; int v3 = (fid & 0xF000) >> 12; int v4 = (art_id(OBJ_TYPE_TILE, 1, 0, 0, 0) & 0xFFF) | v3; fid &= ~0xFFFF; *p = v4 | ((fid >> 16) << 16); p++; } } } } // 0x48431C static int square_load(File* stream, int flags) { int v6; int v7; int v8; int v9; square_reset(); for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { if ((flags & map_data_elev_flags[elevation]) == 0) { int* arr = square[elevation]->field_0; if (db_freadLongCount(stream, arr, SQUARE_GRID_SIZE) != 0) { return -1; } for (int tile = 0; tile < SQUARE_GRID_SIZE; tile++) { v6 = arr[tile]; v6 &= ~(0xFFFF); v6 >>= 16; v7 = (v6 & 0xF000) >> 12; v7 &= ~(0x01); v8 = v6 & 0xFFF; v9 = arr[tile] & 0xFFFF; arr[tile] = ((v8 | (v7 << 12)) << 16) | v9; } } } return 0; } // 0x4843B8 static int map_write_MapData(MapHeader* ptr, File* stream) { if (db_fwriteInt(stream, ptr->version) == -1) return -1; if (db_fwriteByteCount(stream, ptr->name, 16) == -1) return -1; if (db_fwriteInt(stream, ptr->enteringTile) == -1) return -1; if (db_fwriteInt(stream, ptr->enteringElevation) == -1) return -1; if (db_fwriteInt(stream, ptr->enteringRotation) == -1) return -1; if (db_fwriteInt(stream, ptr->localVariablesCount) == -1) return -1; if (db_fwriteInt(stream, ptr->scriptIndex) == -1) return -1; if (db_fwriteInt(stream, ptr->flags) == -1) return -1; if (db_fwriteInt(stream, ptr->darkness) == -1) return -1; if (db_fwriteInt(stream, ptr->globalVariablesCount) == -1) return -1; if (db_fwriteInt(stream, ptr->field_34) == -1) return -1; if (db_fwriteInt(stream, ptr->lastVisitTime) == -1) return -1; if (db_fwriteIntCount(stream, ptr->field_3C, 44) == -1) return -1; return 0; } // 0x4844B4 static int map_read_MapData(MapHeader* ptr, File* stream) { if (db_freadInt(stream, &(ptr->version)) == -1) return -1; if (db_freadByteCount(stream, ptr->name, 16) == -1) return -1; if (db_freadInt(stream, &(ptr->enteringTile)) == -1) return -1; if (db_freadInt(stream, &(ptr->enteringElevation)) == -1) return -1; if (db_freadInt(stream, &(ptr->enteringRotation)) == -1) return -1; if (db_freadInt(stream, &(ptr->localVariablesCount)) == -1) return -1; if (db_freadInt(stream, &(ptr->scriptIndex)) == -1) return -1; if (db_freadInt(stream, &(ptr->flags)) == -1) return -1; if (db_freadInt(stream, &(ptr->darkness)) == -1) return -1; if (db_freadInt(stream, &(ptr->globalVariablesCount)) == -1) return -1; if (db_freadInt(stream, &(ptr->field_34)) == -1) return -1; if (db_freadInt(stream, &(ptr->lastVisitTime)) == -1) return -1; if (db_freadIntCount(stream, ptr->field_3C, 44) == -1) return -1; return 0; } ================================================ FILE: src/game/map.h ================================================ #ifndef FALLOUT_GAME_MAP_H_ #define FALLOUT_GAME_MAP_H_ #include #define WIN32_LEAN_AND_MEAN #include #include "game/combat_defs.h" #include "plib/db/db.h" #include "plib/gnw/rect.h" #include "game/map_defs.h" #include "game/message.h" // TODO: Probably not needed -> replace with array? typedef struct TileData { int field_0[SQUARE_GRID_SIZE]; } TileData; typedef struct MapHeader { // map_ver int version; // map_name char name[16]; // map_ent_tile int enteringTile; // map_ent_elev int enteringElevation; // map_ent_rot int enteringRotation; // map_num_loc_vars int localVariablesCount; // 0map_script_idx int scriptIndex; // map_flags int flags; // map_darkness int darkness; // map_num_glob_vars int globalVariablesCount; // map_number int field_34; // Time in game ticks when PC last visited this map. int lastVisitTime; int field_3C[44]; } MapHeader; typedef struct MapTransition { int map; int elevation; int tile; int rotation; } MapTransition; typedef void IsoWindowRefreshProc(Rect* rect); extern char byte_50B058[]; extern char _aErrorF2[]; extern int map_script_id; extern int* map_local_vars; extern int* map_global_vars; extern int num_map_local_vars; extern int num_map_global_vars; extern int map_elevation; extern TileData square_data[ELEVATION_COUNT]; extern MessageList map_msg_file; extern MapHeader map_data; extern TileData* square[ELEVATION_COUNT]; extern int display_win; int iso_init(); void iso_reset(); void iso_exit(); void map_init(); void map_reset(); void map_exit(); void map_enable_bk_processes(); bool map_disable_bk_processes(); bool map_bk_processes_are_disabled(); int map_set_elevation(int elevation); bool map_is_elevation_empty(int elevation); int map_set_global_var(int var, int value); int map_get_global_var(int var); int map_set_local_var(int var, int value); int map_get_local_var(int var); int map_malloc_local_var(int a1); void map_set_entrance_hex(int a1, int a2, int a3); void map_set_name(const char* name); void map_get_name(char* name); char* map_get_elev_idx(int map_num, int elev); bool is_map_idx_same(int map_num1, int map_num2); int get_map_idx_same(int map_num1, int map_num2); char* map_get_short_name(int map_num); char* map_get_description(); char* map_get_description_idx(int map_index); int map_get_index_number(); int map_scroll(int dx, int dy); char* map_file_path(char* name); int mapSetEntranceInfo(int a1, int a2, int a3); void map_new_map(); int map_load(char* fileName); int map_load_idx(int map_index); int map_load_file(File* stream); int map_load_in_game(char* fileName); static int map_age_dead_critters(); int map_target_load_area(); int map_leave_map(MapTransition* transition); int map_check_state(); void map_fix_critter_combat_data(); int map_save(); int map_save_file(File* stream); int map_save_in_game(bool a1); void map_setup_paths(); #endif /* FALLOUT_GAME_MAP_H_ */ ================================================ FILE: src/game/map_defs.h ================================================ #ifndef MAPDEFS_H #define MAPDEFS_H #include #define ELEVATION_COUNT (3) #define SQUARE_GRID_WIDTH (100) #define SQUARE_GRID_HEIGHT (100) #define SQUARE_GRID_SIZE (SQUARE_GRID_WIDTH * SQUARE_GRID_HEIGHT) #define HEX_GRID_WIDTH (200) #define HEX_GRID_HEIGHT (200) #define HEX_GRID_SIZE (HEX_GRID_WIDTH * HEX_GRID_HEIGHT) static inline bool elevationIsValid(int elevation) { return elevation >= 0 && elevation < ELEVATION_COUNT; } static inline bool squareGridTileIsValid(int tile) { return tile >= 0 && tile < SQUARE_GRID_SIZE; } static inline bool hexGridTileIsValid(int tile) { return tile >= 0 && tile < HEX_GRID_SIZE; } #endif /* MAPDEFS_H */ ================================================ FILE: src/game/message.c ================================================ #include "game/message.h" #include #include #include #include #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "game/gconfig.h" #include "plib/gnw/memory.h" #include "game/roll.h" #define BADWORD_LENGTH_MAX 80 static bool message_find(MessageList* msg, int num, int* out_index); static bool message_add(MessageList* msg, MessageListItem* new_entry); static bool message_parse_number(int* out_num, const char* str); static int message_load_field(File* file, char* str); // 0x519598 static char** bad_word = NULL; // 0x51959C static int bad_total = 0; // 0x5195A0 static int* bad_len = NULL; // Temporary message list item text used during filtering badwords. // // 0x63207C static char bad_copy[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE]; // 0x484770 int init_message() { File* stream = db_fopen("data\\badwords.txt", "rt"); if (stream == NULL) { return -1; } char word[BADWORD_LENGTH_MAX]; bad_total = 0; while (db_fgets(word, BADWORD_LENGTH_MAX - 1, stream)) { bad_total++; } bad_word = (char**)mem_malloc(sizeof(*bad_word) * bad_total); if (bad_word == NULL) { db_fclose(stream); return -1; } bad_len = (int*)mem_malloc(sizeof(*bad_len) * bad_total); if (bad_len == NULL) { mem_free(bad_word); db_fclose(stream); return -1; } db_fseek(stream, 0, SEEK_SET); int index = 0; for (; index < bad_total; index++) { if (!db_fgets(word, BADWORD_LENGTH_MAX - 1, stream)) { break; } int len = strlen(word); if (word[len - 1] == '\n') { len--; word[len] = '\0'; } bad_word[index] = mem_strdup(word); if (bad_word[index] == NULL) { break; } strupr(bad_word[index]); bad_len[index] = len; } db_fclose(stream); if (index != bad_total) { for (; index > 0; index--) { mem_free(bad_word[index - 1]); } mem_free(bad_word); mem_free(bad_len); return -1; } return 0; } // 0x4848F0 void exit_message() { for (int index = 0; index < bad_total; index++) { mem_free(bad_word[index]); } if (bad_total != 0) { mem_free(bad_word); mem_free(bad_len); } bad_total = 0; } // message_init // 0x48494C bool message_init(MessageList* messageList) { if (messageList != NULL) { messageList->entries_num = 0; messageList->entries = NULL; } return true; } // 0x484964 bool message_exit(MessageList* messageList) { int i; MessageListItem* entry; if (messageList == NULL) { return false; } for (i = 0; i < messageList->entries_num; i++) { entry = &(messageList->entries[i]); if (entry->audio != NULL) { mem_free(entry->audio); } if (entry->text != NULL) { mem_free(entry->text); } } messageList->entries_num = 0; if (messageList->entries != NULL) { mem_free(messageList->entries); messageList->entries = NULL; } return true; } // message_load // 0x484AA4 bool message_load(MessageList* messageList, const char* path) { char* language; char localized_path[FILENAME_MAX]; File* file_ptr; char num[1024]; char audio[1024]; char text[1024]; int rc; bool success; MessageListItem entry; success = false; if (messageList == NULL) { return false; } if (path == NULL) { return false; } if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { return false; } sprintf(localized_path, "%s\\%s\\%s", "text", language, path); file_ptr = db_fopen(localized_path, "rt"); if (file_ptr == NULL) { return false; } entry.num = 0; entry.audio = audio; entry.text = text; while (1) { rc = message_load_field(file_ptr, num); if (rc != 0) { break; } if (message_load_field(file_ptr, audio) != 0) { debug_printf("\nError loading audio field.\n", localized_path); goto err; } if (message_load_field(file_ptr, text) != 0) { debug_printf("\nError loading text field.\n", localized_path); goto err; } if (!message_parse_number(&(entry.num), num)) { debug_printf("\nError parsing number.\n", localized_path); goto err; } if (!message_add(messageList, &entry)) { debug_printf("\nError adding message.\n", localized_path); goto err; } } if (rc == 1) { success = true; } err: if (!success) { debug_printf("Error loading message file %s at offset %x.", localized_path, db_ftell(file_ptr)); } db_fclose(file_ptr); return success; } // 0x484C30 bool message_search(MessageList* msg, MessageListItem* entry) { int index; MessageListItem* ptr; if (msg == NULL) { return false; } if (entry == NULL) { return false; } if (msg->entries_num == 0) { return false; } if (!message_find(msg, entry->num, &index)) { return false; } ptr = &(msg->entries[index]); entry->flags = ptr->flags; entry->audio = ptr->audio; entry->text = ptr->text; return true; } // Builds language-aware path in "text" subfolder. // // 0x484CB8 bool message_make_path(char* dest, const char* path) { char* language; if (dest == NULL) { return false; } if (path == NULL) { return false; } if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_LANGUAGE_KEY, &language)) { return false; } sprintf(dest, "%s\\%s\\%s", "text", language, path); return true; } // 0x484D10 bool message_find(MessageList* msg, int num, int* out_index) { int r, l, mid; int cmp; if (msg->entries_num == 0) { *out_index = 0; return false; } r = msg->entries_num - 1; l = 0; do { mid = (l + r) / 2; cmp = num - msg->entries[mid].num; if (cmp == 0) { *out_index = mid; return true; } if (cmp > 0) { l = l + 1; } else { r = r - 1; } } while (r >= l); if (cmp < 0) { *out_index = mid; } else { *out_index = mid + 1; } return false; } // 0x484D68 bool message_add(MessageList* msg, MessageListItem* new_entry) { int index; MessageListItem* entries; MessageListItem* existing_entry; if (message_find(msg, new_entry->num, &index)) { existing_entry = &(msg->entries[index]); if (existing_entry->audio != NULL) { mem_free(existing_entry->audio); } if (existing_entry->text != NULL) { mem_free(existing_entry->text); } } else { if (msg->entries != NULL) { entries = (MessageListItem*)mem_realloc(msg->entries, sizeof(MessageListItem) * (msg->entries_num + 1)); if (entries == NULL) { return false; } msg->entries = entries; if (index != msg->entries_num) { // Move all items below insertion point memmove(&(msg->entries[index + 1]), &(msg->entries[index]), sizeof(MessageListItem) * (msg->entries_num - index)); } } else { msg->entries = (MessageListItem*)mem_malloc(sizeof(MessageListItem)); if (msg->entries == NULL) { return false; } msg->entries_num = 0; index = 0; } existing_entry = &(msg->entries[index]); existing_entry->flags = 0; existing_entry->audio = 0; existing_entry->text = 0; msg->entries_num++; } existing_entry->audio = mem_strdup(new_entry->audio); if (existing_entry->audio == NULL) { return false; } existing_entry->text = mem_strdup(new_entry->text); if (existing_entry->text == NULL) { return false; } existing_entry->num = new_entry->num; return true; } // 0x484F60 bool message_parse_number(int* out_num, const char* str) { const char* ch; bool success; ch = str; if (*ch == '\0') { return false; } success = true; if (*ch == '+' || *ch == '-') { ch++; } while (*ch != '\0') { if (!isdigit(*ch)) { success = false; break; } ch++; } *out_num = atoi(str); return success; } // Read next message file field, the `str` should be at least 1024 bytes long. // // Returns: // 0 - ok // 1 - eof // 2 - mismatched delimeters // 3 - unterminated field // 4 - limit exceeded (> 1024) // // 0x484FB4 int message_load_field(File* file, char* str) { int ch; int len; len = 0; while (1) { ch = db_fgetc(file); if (ch == -1) { return 1; } if (ch == '}') { debug_printf("\nError reading message file - mismatched delimiters.\n"); return 2; } if (ch == '{') { break; } } while (1) { ch = db_fgetc(file); if (ch == -1) { debug_printf("\nError reading message file - EOF reached.\n"); return 3; } if (ch == '}') { *(str + len) = '\0'; return 0; } if (ch != '\n') { *(str + len) = ch; len++; if (len > 1024) { debug_printf("\nError reading message file - text exceeds limit.\n"); return 4; } } } return 0; } // 0x48504C char* getmsg(MessageList* msg, MessageListItem* entry, int num) { // 0x5195A4 static char message_error_str[] = "Error"; entry->num = num; if (!message_search(msg, entry)) { entry->text = message_error_str; debug_printf("\n ** String not found @ getmsg(), MESSAGE.C **\n"); } return entry->text; } // 0x485078 bool message_filter(MessageList* messageList) { // TODO: Check. // 0x50B960 static const char* replacements = "!@#$%&*@#*!&$%#&%#*%!$&%@*$@&"; if (messageList == NULL) { return false; } if (messageList->entries_num == 0) { return true; } if (bad_total == 0) { return true; } int languageFilter = 0; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &languageFilter); if (languageFilter != 1) { return true; } int replacementsCount = strlen(replacements); int replacementsIndex = roll_random(1, replacementsCount) - 1; for (int index = 0; index < messageList->entries_num; index++) { MessageListItem* item = &(messageList->entries[index]); strcpy(bad_copy, item->text); strupr(bad_copy); for (int badwordIndex = 0; badwordIndex < bad_total; badwordIndex++) { // I don't quite understand the loop below. It has no stop // condition besides no matching substring. It also overwrites // already masked words on every iteration. for (char* p = bad_copy;; p++) { const char* substr = strstr(p, bad_word[badwordIndex]); if (substr == NULL) { break; } if (substr == bad_copy || (!isalpha(substr[-1]) && !isalpha(substr[bad_len[badwordIndex]]))) { item->flags |= MESSAGE_LIST_ITEM_TEXT_FILTERED; char* ptr = item->text + (substr - bad_copy); for (int j = 0; j < bad_len[badwordIndex]; j++) { *ptr++ = replacements[replacementsIndex++]; if (replacementsIndex == replacementsCount) { replacementsIndex = 0; } } } } } } return true; } ================================================ FILE: src/game/message.h ================================================ #ifndef FALLOUT_GAME_MESSAGE_H_ #define FALLOUT_GAME_MESSAGE_H_ #include // TODO: Convert to enum. #define MESSAGE_LIST_ITEM_TEXT_FILTERED 0x01 // TODO: Probably should be private. #define MESSAGE_LIST_ITEM_FIELD_MAX_SIZE 1024 typedef struct MessageListItem { int num; int flags; char* audio; char* text; } MessageListItem; typedef struct MessageList { int entries_num; MessageListItem* entries; } MessageList; int init_message(); void exit_message(); bool message_init(MessageList* msg); bool message_exit(MessageList* msg); bool message_load(MessageList* msg, const char* path); bool message_search(MessageList* msg, MessageListItem* entry); bool message_make_path(char* dest, const char* path); char* getmsg(MessageList* msg, MessageListItem* entry, int num); bool message_filter(MessageList* messageList); #endif /* FALLOUT_GAME_MESSAGE_H_ */ ================================================ FILE: src/game/moviefx.c ================================================ #include "game/moviefx.h" #include #include #include #include #include "game/config.h" #include "plib/gnw/debug.h" #include "plib/gnw/memory.h" #include "int/movie.h" #include "game/palette.h" typedef enum MovieEffectType { MOVIE_EFFECT_TYPE_NONE = 0, MOVIE_EFFECT_TYPE_FADE_IN = 1, MOVIE_EFFECT_TYPE_FADE_OUT = 2, } MovieEffectFadeType; typedef struct MovieEffect { int startFrame; int endFrame; int steps; unsigned char fadeType; // range 0-63 unsigned char r; // range 0-63 unsigned char g; // range 0-63 unsigned char b; struct MovieEffect* next; } MovieEffect; static void moviefx_callback_func(int frame); static void moviefx_palette_func(unsigned char* palette, int start, int end); static void moviefx_add(MovieEffect* movie_effect); static void moviefx_remove_all(); // 0x5195F0 static bool moviefx_initialized = false; // 0x5195F4 static MovieEffect* moviefx_effects_list = NULL; // 0x638EC4 static unsigned char source_palette[768]; // 0x6391C4 static bool inside_fade; // 0x487CC0 int moviefx_init() { if (moviefx_initialized) { return -1; } memset(source_palette, 0, sizeof(source_palette)); moviefx_initialized = true; return 0; } // 0x487CF4 void moviefx_reset() { if (!moviefx_initialized) { return; } movieSetCallback(NULL); movieSetPaletteFunc(NULL); moviefx_remove_all(); inside_fade = false; memset(source_palette, 0, sizeof(source_palette)); } // 0x487D30 void moviefx_exit() { if (!moviefx_initialized) { return; } movieSetCallback(NULL); movieSetPaletteFunc(NULL); moviefx_remove_all(); inside_fade = false; memset(source_palette, 0, sizeof(source_palette)); } // 0x487D7C int moviefx_start(const char* filePath) { if (!moviefx_initialized) { return -1; } movieSetCallback(NULL); movieSetPaletteFunc(NULL); moviefx_remove_all(); inside_fade = false; memset(source_palette, 0, sizeof(source_palette)); if (filePath == NULL) { return -1; } Config config; if (!config_init(&config)) { return -1; } int rc = -1; char path[FILENAME_MAX]; strcpy(path, filePath); char* pch = strrchr(path, '.'); if (pch != NULL) { *pch = '\0'; } strcpy(path + strlen(path), ".cfg"); int* movieEffectFrameList; if (!config_load(&config, path, true)) { goto out; } int movieEffectsLength; if (!config_get_value(&config, "info", "total_effects", &movieEffectsLength)) { goto out; } movieEffectFrameList = (int*)mem_malloc(sizeof(*movieEffectFrameList) * movieEffectsLength); if (movieEffectFrameList == NULL) { goto out; } bool frameListRead; if (movieEffectsLength >= 2) { frameListRead = config_get_values(&config, "info", "effect_frames", movieEffectFrameList, movieEffectsLength); } else { frameListRead = config_get_value(&config, "info", "effect_frames", &(movieEffectFrameList[0])); } if (frameListRead) { int movieEffectsCreated = 0; for (int index = 0; index < movieEffectsLength; index++) { char section[20]; itoa(movieEffectFrameList[index], section, 10); char* fadeTypeString; if (!config_get_string(&config, section, "fade_type", &fadeTypeString)) { continue; } int fadeType = MOVIE_EFFECT_TYPE_NONE; if (stricmp(fadeTypeString, "in") == 0) { fadeType = MOVIE_EFFECT_TYPE_FADE_IN; } else if (stricmp(fadeTypeString, "out") == 0) { fadeType = MOVIE_EFFECT_TYPE_FADE_OUT; } if (fadeType == MOVIE_EFFECT_TYPE_NONE) { continue; } int fadeColor[3]; if (!config_get_values(&config, section, "fade_color", fadeColor, 3)) { continue; } int steps; if (!config_get_value(&config, section, "fade_steps", &steps)) { continue; } MovieEffect* movieEffect = (MovieEffect*)mem_malloc(sizeof(*movieEffect)); if (movieEffect == NULL) { continue; } memset(movieEffect, 0, sizeof(*movieEffect)); movieEffect->startFrame = movieEffectFrameList[index]; movieEffect->endFrame = movieEffect->startFrame + steps - 1; movieEffect->steps = steps; movieEffect->fadeType = fadeType & 0xFF; movieEffect->r = fadeColor[0] & 0xFF; movieEffect->g = fadeColor[1] & 0xFF; movieEffect->b = fadeColor[2] & 0xFF; if (movieEffect->startFrame <= 1) { inside_fade = true; } // NOTE: Uninline. moviefx_add(movieEffect); movieEffectsCreated++; } if (movieEffectsCreated != 0) { movieSetCallback(moviefx_callback_func); movieSetPaletteFunc(moviefx_palette_func); rc = 0; } } mem_free(movieEffectFrameList); out: config_exit(&config); return rc; } // 0x4880F0 void moviefx_stop() { if (!moviefx_initialized) { return; } movieSetCallback(NULL); movieSetPaletteFunc(NULL); moviefx_remove_all(); inside_fade = false; memset(source_palette, 0, sizeof(source_palette)); } // 0x488144 static void moviefx_callback_func(int frame) { MovieEffect* movieEffect = moviefx_effects_list; while (movieEffect != NULL) { if (frame >= movieEffect->startFrame && frame <= movieEffect->endFrame) { break; } movieEffect = movieEffect->next; } if (movieEffect != NULL) { unsigned char palette[768]; int step = frame - movieEffect->startFrame + 1; if (movieEffect->fadeType == MOVIE_EFFECT_TYPE_FADE_IN) { for (int index = 0; index < 256; index++) { palette[index * 3] = movieEffect->r - (step * (movieEffect->r - source_palette[index * 3]) / movieEffect->steps); palette[index * 3 + 1] = movieEffect->g - (step * (movieEffect->g - source_palette[index * 3 + 1]) / movieEffect->steps); palette[index * 3 + 2] = movieEffect->b - (step * (movieEffect->b - source_palette[index * 3 + 2]) / movieEffect->steps); } } else { for (int index = 0; index < 256; index++) { palette[index * 3] = source_palette[index * 3] - (step * (source_palette[index * 3] - movieEffect->r) / movieEffect->steps); palette[index * 3 + 1] = source_palette[index * 3 + 1] - (step * (source_palette[index * 3 + 1] - movieEffect->g) / movieEffect->steps); palette[index * 3 + 2] = source_palette[index * 3 + 2] - (step * (source_palette[index * 3 + 2] - movieEffect->b) / movieEffect->steps); } } palette_set_to(palette); } inside_fade = movieEffect != NULL; } // 0x4882AC static void moviefx_palette_func(unsigned char* palette, int start, int end) { memcpy(source_palette + 3 * start, palette, 3 * (end - start + 1)); if (!inside_fade) { palette_set_entries(palette, start, end); } } // NOTE: Inlined. // // 0x4882FC static void moviefx_add(MovieEffect* movie_effect) { movie_effect->next = moviefx_effects_list; moviefx_effects_list = movie_effect; } // 0x488310 static void moviefx_remove_all() { MovieEffect* movieEffect = moviefx_effects_list; while (movieEffect != NULL) { MovieEffect* next = movieEffect->next; mem_free(movieEffect); movieEffect = next; } moviefx_effects_list = NULL; } ================================================ FILE: src/game/moviefx.h ================================================ #ifndef FALLOUT_GAME_MOVIEFX_H_ #define FALLOUT_GAME_MOVIEFX_H_ int moviefx_init(); void moviefx_reset(); void moviefx_exit(); int moviefx_start(const char* fileName); void moviefx_stop(); #endif /* FALLOUT_GAME_MOVIEFX_H_ */ ================================================ FILE: src/game/object.c ================================================ #include "game/object.h" #include #include #include "game/anim.h" #include "game/art.h" #include "plib/color/color.h" #include "game/combat.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/item.h" #include "game/light.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/party.h" #include "game/proto.h" #include "game/protinst.h" #include "game/scripts.h" #include "game/textobj.h" #include "game/tile.h" #include "game/worldmap.h" #include "plib/gnw/svga.h" static int obj_read_obj(Object* obj, File* stream); static int obj_load_func(File* stream); static void obj_fix_combat_cid_for_dude(); static void object_fix_weapon_ammo(Object* obj); static int obj_write_obj(Object* obj, File* stream); static int obj_offset_table_init(); static void obj_offset_table_exit(); static int obj_order_table_init(); static int obj_order_comp_func_even(const void* a1, const void* a2); static int obj_order_comp_func_odd(const void* a1, const void* a2); static void obj_order_table_exit(); static int obj_render_table_init(); static void obj_render_table_exit(); static void obj_light_table_init(); static void obj_blend_table_init(); static void obj_blend_table_exit(); static int obj_create_object(Object** objectPtr); static void obj_destroy_object(Object** objectPtr); static int obj_create_object_node(ObjectListNode** nodePtr); static void obj_destroy_object_node(ObjectListNode** nodePtr); static int obj_node_ptr(Object* obj, ObjectListNode** out_node, ObjectListNode** out_prev_node); static void obj_insert(ObjectListNode* ptr); static int obj_remove(ObjectListNode* a1, ObjectListNode* a2); static int obj_connect_to_tile(ObjectListNode* node, int tile_index, int elev, Rect* rect); static int obj_adjust_light(Object* obj, int a2, Rect* rect); static void obj_render_outline(Object* object, Rect* rect); static void obj_render_object(Object* object, Rect* rect, int light); static int obj_preload_sort(const void* a1, const void* a2); // 0x5195F8 static bool objInitialized = false; // 0x5195FC static int updateHexWidth = 0; // 0x519600 static int updateHexHeight = 0; // 0x519604 static int updateHexArea = 0; // 0x519608 static int* orderTable[2] = { NULL, NULL, }; // 0x519610 static int* offsetTable[2] = { NULL, NULL, }; // 0x519618 static int* offsetDivTable = NULL; // 0x51961C static int* offsetModTable = NULL; // 0x519620 static ObjectListNode** renderTable = NULL; // 0x519624 static int outlineCount = 0; // Contains objects that are not bounded to tiles. // // 0x519628 static ObjectListNode* floatingObjects = NULL; // 0x51962C static int centerToUpperLeft = 0; // 0x519630 static int find_elev = 0; // 0x519634 static int find_tile = 0; // 0x519638 static ObjectListNode* find_ptr = NULL; // 0x51963C static int* preload_list = NULL; // 0x519640 static int preload_list_index = 0; // 0x51964C static Rect light_rect[9] = { { 0, 0, 96, 42 }, { 0, 0, 160, 74 }, { 0, 0, 224, 106 }, { 0, 0, 288, 138 }, { 0, 0, 352, 170 }, { 0, 0, 416, 202 }, { 0, 0, 480, 234 }, { 0, 0, 544, 266 }, { 0, 0, 608, 298 }, }; // 0x5196DC static int light_distance[36] = { 1, 2, 3, 4, 5, 6, 7, 8, 2, 3, 4, 5, 6, 7, 8, 3, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8, 5, 6, 7, 8, 6, 7, 8, 7, 8, 8, }; // 0x51976C static int fix_violence_level = -1; // 0x519770 static int obj_last_roof_x = -1; // 0x519774 static int obj_last_roof_y = -1; // 0x519778 static int obj_last_elev = -1; // 0x51977C static bool obj_last_is_empty = true; // 0x519780 unsigned char* wallBlendTable = NULL; // 0x519784 unsigned char* glassBlendTable = NULL; // 0x519788 unsigned char* steamBlendTable = NULL; // 0x51978C unsigned char* energyBlendTable = NULL; // 0x519790 unsigned char* redBlendTable = NULL; // 0x519794 Object* moveBlockObj = NULL; // 0x519798 static int objItemOutlineState = 0; // 0x6391D0 static int light_blocked[6][36]; // 0x639530 static int light_offsets[2][6][36]; // 0x639BF0 static Rect buf_rect; // 0x639C00 static Object* outlinedObjects[100]; // 0x639D90 static Rect updateAreaPixelBounds; // Contains objects that are bounded to tiles. // // 0x639DA0 static ObjectListNode* objectTable[HEX_GRID_SIZE]; // 0x660EA0 unsigned char glassGrayTable[256]; // 0x660FA0 unsigned char commonGrayTable[256]; // 0x6610A0 static int buf_size; // 0x6610A4 static unsigned char* back_buf; // 0x6610A8 static int buf_length; // Translucent "egg" effect around player. // // 0x6610AC Object* obj_egg; // 0x6610B0 static int buf_full; // 0x6610B4 static int buf_width; // 0x6610B8 Object* obj_dude; // 0x6610BC static char obj_seen_check[5001]; // 0x662445 static char obj_seen[5001]; // obj_init // 0x488780 int obj_init(unsigned char* buf, int width, int height, int pitch) { int dudeFid; int eggFid; memset(obj_seen, 0, 5001); updateAreaPixelBounds.lrx = width + 320; updateAreaPixelBounds.ulx = -320; updateAreaPixelBounds.lry = height + 240; updateAreaPixelBounds.uly = -240; updateHexWidth = (updateAreaPixelBounds.lrx + 320 + 1) / 32 + 1; updateHexHeight = (updateAreaPixelBounds.lry + 240 + 1) / 12 + 1; updateHexArea = updateHexWidth * updateHexHeight; memset(objectTable, 0, sizeof(objectTable)); if (obj_offset_table_init() == -1) { return -1; } if (obj_order_table_init() == -1) { goto err; } if (obj_render_table_init() == -1) { goto err_2; } if (light_init() == -1) { goto err_2; } if (text_object_init(buf, width, height) == -1) { goto err_2; } obj_light_table_init(); obj_blend_table_init(); centerToUpperLeft = tile_num(updateAreaPixelBounds.ulx, updateAreaPixelBounds.uly, 0) - tile_center_tile; buf_width = width; buf_length = height; back_buf = buf; buf_rect.ulx = 0; buf_rect.uly = 0; buf_rect.lrx = width - 1; buf_rect.lry = height - 1; buf_size = height * width; buf_full = pitch; dudeFid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, 0, 0); obj_new(&obj_dude, dudeFid, 0x1000000); obj_dude->flags |= OBJECT_FLAG_0x400; obj_dude->flags |= OBJECT_TEMPORARY; obj_dude->flags |= OBJECT_HIDDEN; obj_dude->flags |= OBJECT_LIGHT_THRU; obj_set_light(obj_dude, 4, 0x10000, NULL); if (partyMemberAdd(obj_dude) == -1) { debug_printf("\n Error: Can't add Player into party!"); exit(1); } eggFid = art_id(OBJ_TYPE_INTERFACE, 2, 0, 0, 0); obj_new(&obj_egg, eggFid, -1); obj_egg->flags |= OBJECT_FLAG_0x400; obj_egg->flags |= OBJECT_TEMPORARY; obj_egg->flags |= OBJECT_HIDDEN; obj_egg->flags |= OBJECT_LIGHT_THRU; objInitialized = true; return 0; err_2: // NOTE: Uninline. obj_order_table_exit(); err: obj_offset_table_exit(); return -1; } // 0x488A00 void obj_reset() { if (objInitialized) { text_object_reset(); obj_remove_all(); memset(obj_seen, 0, 5001); light_reset(); } } // 0x488A30 void obj_exit() { if (objInitialized) { obj_dude->flags &= ~OBJECT_FLAG_0x400; obj_egg->flags &= ~OBJECT_FLAG_0x400; obj_remove_all(); text_object_exit(); // NOTE: Uninline. obj_blend_table_exit(); light_exit(); // NOTE: Uninline. obj_render_table_exit(); // NOTE: Uninline. obj_order_table_exit(); obj_offset_table_exit(); } } // 0x488AF4 static int obj_read_obj(Object* obj, File* stream) { int field_74; if (db_freadInt(stream, &(obj->id)) == -1) return -1; if (db_freadInt(stream, &(obj->tile)) == -1) return -1; if (db_freadInt(stream, &(obj->x)) == -1) return -1; if (db_freadInt(stream, &(obj->y)) == -1) return -1; if (db_freadInt(stream, &(obj->sx)) == -1) return -1; if (db_freadInt(stream, &(obj->sy)) == -1) return -1; if (db_freadInt(stream, &(obj->frame)) == -1) return -1; if (db_freadInt(stream, &(obj->rotation)) == -1) return -1; if (db_freadInt(stream, &(obj->fid)) == -1) return -1; if (db_freadInt(stream, &(obj->flags)) == -1) return -1; if (db_freadInt(stream, &(obj->elevation)) == -1) return -1; if (db_freadInt(stream, &(obj->pid)) == -1) return -1; if (db_freadInt(stream, &(obj->cid)) == -1) return -1; if (db_freadInt(stream, &(obj->lightDistance)) == -1) return -1; if (db_freadInt(stream, &(obj->lightIntensity)) == -1) return -1; if (db_freadInt(stream, &field_74) == -1) return -1; if (db_freadInt(stream, &(obj->sid)) == -1) return -1; if (db_freadInt(stream, &(obj->field_80)) == -1) return -1; obj->outline = 0; obj->owner = NULL; if (proto_read_protoUpdateData(obj, stream) != 0) { return -1; } if (obj->pid < 0x5000010 || obj->pid > 0x5000017) { if (PID_TYPE(obj->pid) == 0 && !(map_data.flags & 0x01)) { object_fix_weapon_ammo(obj); } } else { if (obj->data.misc.map <= 0) { if ((obj->fid & 0xFFF) < 33) { obj->fid = art_id(OBJ_TYPE_MISC, (obj->fid & 0xFFF) + 16, FID_ANIM_TYPE(obj->fid), 0, 0); } } } return 0; } // 0x488CE4 int obj_load(File* stream) { int rc = obj_load_func(stream); fix_violence_level = -1; return rc; } // 0x488CF8 static int obj_load_func(File* stream) { if (stream == NULL) { return -1; } bool fixMapInventory; if (!configGetBool(&game_config, GAME_CONFIG_MAPPER_KEY, GAME_CONFIG_FIX_MAP_INVENTORY_KEY, &fixMapInventory)) { fixMapInventory = false; } if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &fix_violence_level)) { fix_violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD; } int objectCount; if (db_freadInt(stream, &objectCount) == -1) { return -1; } if (preload_list != NULL) { mem_free(preload_list); } if (objectCount != 0) { preload_list = (int*)mem_malloc(sizeof(*preload_list) * objectCount); memset(preload_list, 0, sizeof(*preload_list) * objectCount); if (preload_list == NULL) { return -1; } preload_list_index = 0; } for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { int objectCountAtElevation; if (db_freadInt(stream, &objectCountAtElevation) == -1) { return -1; } for (int objectIndex = 0; objectIndex < objectCountAtElevation; objectIndex++) { ObjectListNode* objectListNode; // NOTE: Uninline. if (obj_create_object_node(&objectListNode) == -1) { return -1; } if (obj_create_object(&(objectListNode->obj)) == -1) { // NOTE: Uninline. obj_destroy_object_node(&objectListNode); return -1; } if (obj_read_obj(objectListNode->obj, stream) != 0) { // NOTE: Uninline. obj_destroy_object(&(objectListNode->obj)); // NOTE: Uninline. obj_destroy_object_node(&objectListNode); return -1; } objectListNode->obj->outline = 0; preload_list[preload_list_index++] = objectListNode->obj->fid; if (objectListNode->obj->sid != -1) { Script* script; if (scr_ptr(objectListNode->obj->sid, &script) == -1) { objectListNode->obj->sid = -1; debug_printf("\nError connecting object to script!"); } else { script->owner = objectListNode->obj; objectListNode->obj->field_80 = script->field_14; } } obj_fix_violence_settings(&(objectListNode->obj->fid)); objectListNode->obj->elevation = elevation; obj_insert(objectListNode); if ((objectListNode->obj->flags & OBJECT_FLAG_0x400) && PID_TYPE(objectListNode->obj->pid) == OBJ_TYPE_CRITTER && objectListNode->obj->pid != 18000) { objectListNode->obj->flags &= ~OBJECT_FLAG_0x400; } Inventory* inventory = &(objectListNode->obj->data.inventory); if (inventory->length != 0) { inventory->items = (InventoryItem*)mem_malloc(sizeof(InventoryItem) * inventory->capacity); if (inventory->items == NULL) { return -1; } for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) { InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]); if (db_freadInt(stream, &(inventoryItem->quantity)) != 0) { debug_printf("Error loading inventory\n"); return -1; } if (fixMapInventory) { inventoryItem->item = (Object*)mem_malloc(sizeof(Object)); if (inventoryItem->item == NULL) { debug_printf("Error loading inventory\n"); return -1; } if (obj_read_obj(inventoryItem->item, stream) != 0) { debug_printf("Error loading inventory\n"); return -1; } } else { if (obj_load_obj(stream, &(inventoryItem->item), elevation, objectListNode->obj) == -1) { return -1; } } } } else { inventory->capacity = 0; inventory->items = NULL; } } } obj_rebuild_all_light(); return 0; } // 0x48909C static void obj_fix_combat_cid_for_dude() { Object** critterList; int critterListLength = obj_create_list(-1, map_elevation, OBJ_TYPE_CRITTER, &critterList); if (obj_dude->data.critter.combat.whoHitMeCid == -1) { obj_dude->data.critter.combat.whoHitMe = NULL; } else { int index = find_cid(0, obj_dude->data.critter.combat.whoHitMeCid, critterList, critterListLength); if (index != critterListLength) { obj_dude->data.critter.combat.whoHitMe = critterList[index]; } else { obj_dude->data.critter.combat.whoHitMe = NULL; } } if (critterListLength != 0) { // NOTE: Uninline. obj_delete_list(critterList); } } // Fixes ammo pid and number of charges. // // 0x48911C static void object_fix_weapon_ammo(Object* obj) { if (PID_TYPE(obj->pid) != OBJ_TYPE_ITEM) { return; } Proto* proto; if (proto_ptr(obj->pid, &proto) == -1) { debug_printf("\nError: obj_load: proto_ptr failed on pid"); exit(1); } int charges; if (item_get_type(obj) == ITEM_TYPE_WEAPON) { int ammoTypePid = obj->data.item.weapon.ammoTypePid; if (ammoTypePid == 0xCCCCCCCC || ammoTypePid == -1) { obj->data.item.weapon.ammoTypePid = proto->item.data.weapon.ammoTypePid; } charges = obj->data.item.weapon.ammoQuantity; if (charges == 0xCCCCCCCC || charges == -1 || charges != proto->item.data.weapon.ammoCapacity) { obj->data.item.weapon.ammoQuantity = proto->item.data.weapon.ammoCapacity; } } else { if (PID_TYPE(obj->pid) == OBJ_TYPE_MISC) { // FIXME: looks like this code in unreachable charges = obj->data.item.misc.charges; if (charges == 0xCCCCCCCC) { charges = proto->item.data.misc.charges; obj->data.item.misc.charges = charges; if (charges == 0xCCCCCCCC) { debug_printf("\nError: Misc Item Prototype %s: charges incorrect!", proto_name(obj->pid)); obj->data.item.misc.charges = 0; } } else { if (charges != proto->item.data.misc.charges) { obj->data.item.misc.charges = proto->item.data.misc.charges; } } } } } // 0x489200 static int obj_write_obj(Object* obj, File* stream) { if (db_fwriteInt(stream, obj->id) == -1) return -1; if (db_fwriteInt(stream, obj->tile) == -1) return -1; if (db_fwriteInt(stream, obj->x) == -1) return -1; if (db_fwriteInt(stream, obj->y) == -1) return -1; if (db_fwriteInt(stream, obj->sx) == -1) return -1; if (db_fwriteInt(stream, obj->sy) == -1) return -1; if (db_fwriteInt(stream, obj->frame) == -1) return -1; if (db_fwriteInt(stream, obj->rotation) == -1) return -1; if (db_fwriteInt(stream, obj->fid) == -1) return -1; if (db_fwriteInt(stream, obj->flags) == -1) return -1; if (db_fwriteInt(stream, obj->elevation) == -1) return -1; if (db_fwriteInt(stream, obj->pid) == -1) return -1; if (db_fwriteInt(stream, obj->cid) == -1) return -1; if (db_fwriteInt(stream, obj->lightDistance) == -1) return -1; if (db_fwriteInt(stream, obj->lightIntensity) == -1) return -1; if (db_fwriteInt(stream, obj->outline) == -1) return -1; if (db_fwriteInt(stream, obj->sid) == -1) return -1; if (db_fwriteInt(stream, obj->field_80) == -1) return -1; if (proto_write_protoUpdateData(obj, stream) == -1) return -1; return 0; } // 0x48935C int obj_save(File* stream) { if (stream == NULL) { return -1; } obj_process_seen(); int objectCount = 0; long objectCountPos = db_ftell(stream); if (db_fwriteInt(stream, objectCount) == -1) { return -1; } for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { int objectCountAtElevation = 0; long objectCountAtElevationPos = db_ftell(stream); if (db_fwriteInt(stream, objectCountAtElevation) == -1) { return -1; } for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { for (ObjectListNode* objectListNode = objectTable[tile]; objectListNode != NULL; objectListNode = objectListNode->next) { Object* object = objectListNode->obj; if (object->elevation != elevation) { continue; } if ((object->flags & OBJECT_TEMPORARY) != 0) { continue; } CritterCombatData* combatData = NULL; Object* whoHitMe = NULL; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combatData = &(object->data.critter.combat); whoHitMe = combatData->whoHitMe; if (whoHitMe != 0) { if (combatData->whoHitMeCid != -1) { combatData->whoHitMeCid = whoHitMe->cid; } } else { combatData->whoHitMeCid = -1; } } if (obj_write_obj(object, stream) == -1) { return -1; } if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combatData->whoHitMe = whoHitMe; } Inventory* inventory = &(object->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (db_fwriteInt(stream, inventoryItem->quantity) == -1) { return -1; } if (obj_save_obj(stream, inventoryItem->item) == -1) { return -1; } } objectCountAtElevation++; } } long pos = db_ftell(stream); db_fseek(stream, objectCountAtElevationPos, SEEK_SET); db_fwriteInt(stream, objectCountAtElevation); db_fseek(stream, pos, SEEK_SET); objectCount += objectCountAtElevation; } long pos = db_ftell(stream); db_fseek(stream, objectCountPos, SEEK_SET); db_fwriteInt(stream, objectCount); db_fseek(stream, pos, SEEK_SET); return 0; } // 0x489550 void obj_render_pre_roof(Rect* rect, int elevation) { if (!objInitialized) { return; } Rect updatedRect; if (rect_inside_bound(rect, &buf_rect, &updatedRect) != 0) { return; } int ambientLight = light_get_ambient(); int minX = updatedRect.ulx - 320; int minY = updatedRect.uly - 240; int maxX = updatedRect.lrx + 320; int maxY = updatedRect.lry + 240; int topLeftTile = tile_num(minX, minY, elevation); int updateAreaHexWidth = (maxX - minX + 1) / 32; int updateAreaHexHeight = (maxY - minY + 1) / 12; int parity = tile_center_tile & 1; int* orders = orderTable[parity]; int* offsets = offsetTable[parity]; outlineCount = 0; int renderCount = 0; for (int i = 0; i < updateHexArea; i++) { int offsetIndex = *orders++; if (updateAreaHexHeight > offsetDivTable[offsetIndex] && updateAreaHexWidth > offsetModTable[offsetIndex]) { int light; ObjectListNode* objectListNode = objectTable[topLeftTile + offsets[offsetIndex]]; if (objectListNode != NULL) { // NOTE: calls light_get_tile two times, probably result of min/max macro int tileLight = light_get_tile(elevation, objectListNode->obj->tile); if (tileLight >= ambientLight) { light = tileLight; } else { light = ambientLight; } } while (objectListNode != NULL) { if (elevation < objectListNode->obj->elevation) { break; } if (elevation == objectListNode->obj->elevation) { if ((objectListNode->obj->flags & OBJECT_FLAT) == 0) { break; } if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) { obj_render_object(objectListNode->obj, &updatedRect, light); if ((objectListNode->obj->outline & OUTLINE_TYPE_MASK) != 0) { if ((objectListNode->obj->outline & OUTLINE_DISABLED) == 0 && outlineCount < 100) { outlinedObjects[outlineCount++] = objectListNode->obj; } } } } objectListNode = objectListNode->next; } if (objectListNode != NULL) { renderTable[renderCount++] = objectListNode; } } } for (int i = 0; i < renderCount; i++) { int light; ObjectListNode* objectListNode = renderTable[i]; if (objectListNode != NULL) { // NOTE: calls light_get_tile two times, probably result of min/max macro int tileLight = light_get_tile(elevation, objectListNode->obj->tile); if (tileLight >= ambientLight) { light = tileLight; } else { light = ambientLight; } } while (objectListNode != NULL) { Object* object = objectListNode->obj; if (elevation < object->elevation) { break; } if (elevation == objectListNode->obj->elevation) { if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) { obj_render_object(object, &updatedRect, light); if ((objectListNode->obj->outline & OUTLINE_TYPE_MASK) != 0) { if ((objectListNode->obj->outline & OUTLINE_DISABLED) == 0 && outlineCount < 100) { outlinedObjects[outlineCount++] = objectListNode->obj; } } } } objectListNode = objectListNode->next; } } } // 0x4897EC void obj_render_post_roof(Rect* rect, int elevation) { if (!objInitialized) { return; } Rect updatedRect; if (rect_inside_bound(rect, &buf_rect, &updatedRect) != 0) { return; } for (int index = 0; index < outlineCount; index++) { obj_render_outline(outlinedObjects[index], &updatedRect); } text_object_render(&updatedRect); ObjectListNode* objectListNode = floatingObjects; while (objectListNode != NULL) { Object* object = objectListNode->obj; if ((object->flags & OBJECT_HIDDEN) == 0) { obj_render_object(object, &updatedRect, 0x10000); } objectListNode = objectListNode->next; } } // 0x489A84 int obj_new(Object** objectPtr, int fid, int pid) { ObjectListNode* objectListNode; // NOTE: Uninline; if (obj_create_object_node(&objectListNode) == -1) { return -1; } if (obj_create_object(&(objectListNode->obj)) == -1) { // Uninline. obj_destroy_object_node(&objectListNode); return -1; } objectListNode->obj->fid = fid; obj_insert(objectListNode); if (objectPtr) { *objectPtr = objectListNode->obj; } objectListNode->obj->pid = pid; objectListNode->obj->id = new_obj_id(); if (pid == -1 || PID_TYPE(pid) == OBJ_TYPE_TILE) { Inventory* inventory = &(objectListNode->obj->data.inventory); inventory->length = 0; inventory->items = NULL; return 0; } proto_update_init(objectListNode->obj); Proto* proto = NULL; if (proto_ptr(pid, &proto) == -1) { return 0; } obj_set_light(objectListNode->obj, proto->lightDistance, proto->lightIntensity, NULL); if ((proto->flags & 0x08) != 0) { obj_toggle_flat(objectListNode->obj, NULL); } if ((proto->flags & 0x10) != 0) { objectListNode->obj->flags |= OBJECT_NO_BLOCK; } if ((proto->flags & 0x800) != 0) { objectListNode->obj->flags |= OBJECT_MULTIHEX; } if ((proto->flags & 0x8000) != 0) { objectListNode->obj->flags |= OBJECT_TRANS_NONE; } else { if ((proto->flags & 0x10000) != 0) { objectListNode->obj->flags |= OBJECT_TRANS_WALL; } else if ((proto->flags & 0x20000) != 0) { objectListNode->obj->flags |= OBJECT_TRANS_GLASS; } else if ((proto->flags & 0x40000) != 0) { objectListNode->obj->flags |= OBJECT_TRANS_STEAM; } else if ((proto->flags & 0x80000) != 0) { objectListNode->obj->flags |= OBJECT_TRANS_ENERGY; } else if ((proto->flags & 0x4000) != 0) { objectListNode->obj->flags |= OBJECT_TRANS_RED; } } if ((proto->flags & 0x20000000) != 0) { objectListNode->obj->flags |= OBJECT_LIGHT_THRU; } if ((proto->flags & 0x80000000) != 0) { objectListNode->obj->flags |= OBJECT_SHOOT_THRU; } if ((proto->flags & 0x10000000) != 0) { objectListNode->obj->flags |= OBJECT_WALL_TRANS_END; } if ((proto->flags & 0x1000) != 0) { objectListNode->obj->flags |= OBJECT_NO_HIGHLIGHT; } obj_new_sid(objectListNode->obj, &(objectListNode->obj->sid)); return 0; } // 0x489C9C int obj_pid_new(Object** objectPtr, int pid) { Proto* proto; *objectPtr = NULL; if (proto_ptr(pid, &proto) == -1) { return -1; } return obj_new(objectPtr, proto->fid, pid); } // 0x489CCC int obj_copy(Object** a1, Object* a2) { if (a2 == NULL) { return -1; } ObjectListNode* objectListNode; // NOTE: Uninline. if (obj_create_object_node(&objectListNode) == -1) { return -1; } if (obj_create_object(&(objectListNode->obj)) == -1) { // NOTE: Uninline. obj_destroy_object_node(&objectListNode); return -1; } clear_pupdate_data(objectListNode->obj); memcpy(objectListNode->obj, a2, sizeof(Object)); if (a1 != NULL) { *a1 = objectListNode->obj; } obj_insert(objectListNode); objectListNode->obj->id = new_obj_id(); if (objectListNode->obj->sid != -1) { objectListNode->obj->sid = -1; obj_new_sid(objectListNode->obj, &(objectListNode->obj->sid)); } if (obj_set_rotation(objectListNode->obj, a2->rotation, NULL) == -1) { // TODO: Probably leaking object allocated with obj_create_object. // NOTE: Uninline. obj_destroy_object_node(&objectListNode); return -1; } objectListNode->obj->flags &= ~OBJECT_USED; Inventory* newInventory = &(objectListNode->obj->data.inventory); newInventory->length = 0; newInventory->capacity = 0; Inventory* oldInventory = &(a2->data.inventory); for (int index = 0; index < oldInventory->length; index++) { InventoryItem* oldInventoryItem = &(oldInventory->items[index]); Object* newItem; if (obj_copy(&newItem, oldInventoryItem->item) == -1) { // TODO: Probably leaking object allocated with obj_create_object. // NOTE: Uninline. obj_destroy_object_node(&objectListNode); return -1; } if (item_add_force(objectListNode->obj, newItem, oldInventoryItem->quantity) == 1) { // TODO: Probably leaking object allocated with obj_create_object. // NOTE: Uninline. obj_destroy_object_node(&objectListNode); return -1; } } return 0; } // 0x489EC4 int obj_connect(Object* object, int tile, int elevation, Rect* rect) { if (object == NULL) { return -1; } if (!hexGridTileIsValid(tile)) { return -1; } if (!elevationIsValid(elevation)) { return -1; } ObjectListNode* objectListNode; // NOTE: Uninline. if (obj_create_object_node(&objectListNode) == -1) { return -1; } objectListNode->obj = object; return obj_connect_to_tile(objectListNode, tile, elevation, rect); } // 0x489F34 int obj_disconnect(Object* obj, Rect* rect) { if (obj == NULL) { return -1; } ObjectListNode* node; ObjectListNode* prev_node; if (obj_node_ptr(obj, &node, &prev_node) != 0) { return -1; } if (obj_adjust_light(obj, 1, rect) == -1) { if (rect != NULL) { obj_bound(obj, rect); } } if (prev_node != NULL) { prev_node->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } if (node != NULL) { mem_free(node); } obj->tile = -1; return 0; } // 0x489FF8 int obj_offset(Object* obj, int x, int y, Rect* rect) { if (obj == NULL) { return -1; } ObjectListNode* node = NULL; ObjectListNode* previousNode = NULL; if (obj_node_ptr(obj, &node, &previousNode) == -1) { return -1; } if (obj == obj_dude) { if (rect != NULL) { Rect eggRect; obj_bound(obj_egg, &eggRect); rectCopy(rect, &eggRect); if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } obj->x += x; obj->sx += x; obj->y += y; obj->sy += y; obj_insert(node); rectOffset(&eggRect, x, y); obj_offset(obj_egg, x, y, NULL); rect_min_bound(rect, &eggRect, rect); } else { if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } obj->x += x; obj->sx += x; obj->y += y; obj->sy += y; obj_insert(node); obj_offset(obj_egg, x, y, NULL); } } else { if (rect != NULL) { obj_bound(obj, rect); if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } obj->x += x; obj->sx += x; obj->y += y; obj->sy += y; obj_insert(node); Rect objectRect; rectCopy(&objectRect, rect); rectOffset(&objectRect, x, y); rect_min_bound(rect, &objectRect, rect); } else { if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } obj->x += x; obj->sx += x; obj->y += y; obj->sy += y; obj_insert(node); } } return 0; } // 0x48A324 int obj_move(Object* a1, int a2, int a3, int elevation, Rect* a5) { if (a1 == NULL) { return -1; } // TODO: Get rid of initialization. ObjectListNode* node = NULL; ObjectListNode* previousNode; int v22 = 0; int tile = a1->tile; if (hexGridTileIsValid(tile)) { if (obj_node_ptr(a1, &node, &previousNode) == -1) { return -1; } if (obj_adjust_light(a1, 1, a5) == -1) { if (a5 != NULL) { obj_bound(a1, a5); } } if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } a1->tile = -1; a1->elevation = elevation; v22 = 1; } else { if (elevation == a1->elevation) { if (a5 != NULL) { obj_bound(a1, a5); } } else { if (obj_node_ptr(a1, &node, &previousNode) == -1) { return -1; } if (a5 != NULL) { obj_bound(a1, a5); } if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile != -1) { objectTable[tile] = objectTable[tile]->next; } else { floatingObjects = floatingObjects->next; } } a1->elevation = elevation; v22 = 1; } } CacheEntry* cacheHandle; int width; int height; Art* art = art_ptr_lock(a1->fid, &cacheHandle); if (art != NULL) { art_frame_width_length(art, a1->frame, a1->rotation, &width, &height); a1->sx = a2 - width / 2; a1->sy = a3 - (height - 1); art_ptr_unlock(cacheHandle); } if (v22) { obj_insert(node); } if (a5 != NULL) { Rect rect; obj_bound(a1, &rect); rect_min_bound(a5, &rect, a5); } if (a1 == obj_dude) { if (a1 != NULL) { Rect rect; obj_move(obj_egg, a2, a3, elevation, &rect); rect_min_bound(a5, &rect, a5); } else { obj_move(obj_egg, a2, a3, elevation, NULL); } } return 0; } // 0x48A568 int obj_move_to_tile(Object* obj, int tile, int elevation, Rect* rect) { if (obj == NULL) { return -1; } if (!hexGridTileIsValid(tile)) { return -1; } if (!elevationIsValid(elevation)) { return -1; } ObjectListNode* node; ObjectListNode* prevNode; if (obj_node_ptr(obj, &node, &prevNode) == -1) { return -1; } Rect v23; int v5 = obj_adjust_light(obj, 1, rect); if (rect != NULL) { if (v5 == -1) { obj_bound(obj, rect); } rectCopy(&v23, rect); } int oldElevation = obj->elevation; if (prevNode != NULL) { prevNode->next = node->next; } else { int tileIndex = node->obj->tile; if (tileIndex == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tileIndex] = objectTable[tileIndex]->next; } } if (obj_connect_to_tile(node, tile, elevation, rect) == -1) { return -1; } if (isInCombat()) { if (FID_TYPE(obj->fid) == OBJ_TYPE_CRITTER) { bool v8 = obj->outline != 0 && (obj->outline & OUTLINE_DISABLED) == 0; combat_update_critter_outline_for_los(obj, v8); } } if (rect != NULL) { rect_min_bound(rect, &v23, rect); } if (obj == obj_dude) { ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { Object* obj = objectListNode->obj; int elev = obj->elevation; if (elevation < elev) { break; } if (elevation == elev) { if (FID_TYPE(obj->fid) == OBJ_TYPE_MISC) { if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) { ObjectData* data = &(obj->data); MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = data->misc.map; transition.tile = data->misc.tile; transition.elevation = data->misc.elevation; transition.rotation = data->misc.rotation; map_leave_map(&transition); wmMapMarkMapEntranceState(transition.map, transition.elevation, 1); } } } objectListNode = objectListNode->next; } // NOTE: Uninline. obj_set_seen(tile); int roofX = tile % 200 / 2; int roofY = tile / 200 / 2; if (roofX != obj_last_roof_x || roofY != obj_last_roof_y || elevation != obj_last_elev) { int currentSquare = square[elevation]->field_0[roofX + 100 * roofY]; int currentSquareFid = art_id(OBJ_TYPE_TILE, (currentSquare >> 16) & 0xFFF, 0, 0, 0); int previousSquare = square[elevation]->field_0[obj_last_roof_x + 100 * obj_last_roof_y]; bool isEmpty = art_id(OBJ_TYPE_TILE, 1, 0, 0, 0) == currentSquareFid; if (isEmpty != obj_last_is_empty || (((currentSquare >> 16) & 0xF000) >> 12) != (((previousSquare >> 16) & 0xF000) >> 12)) { if (!obj_last_is_empty) { tile_fill_roof(obj_last_roof_x, obj_last_roof_y, elevation, 1); } if (!isEmpty) { tile_fill_roof(roofX, roofY, elevation, 0); } if (rect != NULL) { rect_min_bound(rect, &scr_size, rect); } } obj_last_roof_x = roofX; obj_last_roof_y = roofY; obj_last_elev = elevation; obj_last_is_empty = isEmpty; } if (rect != NULL) { Rect r; obj_move_to_tile(obj_egg, tile, elevation, &r); rect_min_bound(rect, &r, rect); } else { obj_move_to_tile(obj_egg, tile, elevation, 0); } if (elevation != oldElevation) { map_set_elevation(elevation); tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS); if (isInCombat()) { game_user_wants_to_quit = 1; } } } else { if (elevation != obj_last_elev && PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { combat_delete_critter(obj); } } return 0; } // 0x48A9A0 int obj_reset_roof() { int fid = art_id(OBJ_TYPE_TILE, (square[obj_dude->elevation]->field_0[obj_last_roof_x + 100 * obj_last_roof_y] >> 16) & 0xFFF, 0, 0, 0); if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { tile_fill_roof(obj_last_roof_x, obj_last_roof_y, obj_dude->elevation, 1); } return 0; } // Sets object fid. // // 0x48AA3C int obj_change_fid(Object* obj, int fid, Rect* dirtyRect) { Rect new_rect; if (obj == NULL) { return -1; } if (dirtyRect != NULL) { obj_bound(obj, dirtyRect); obj->fid = fid; obj_bound(obj, &new_rect); rect_min_bound(dirtyRect, &new_rect, dirtyRect); } else { obj->fid = fid; } return 0; } // Sets object frame. // // 0x48AA84 int obj_set_frame(Object* obj, int frame, Rect* rect) { Rect new_rect; Art* art; CacheEntry* cache_entry; int framesPerDirection; if (obj == NULL) { return -1; } art = art_ptr_lock(obj->fid, &cache_entry); if (art == NULL) { return -1; } framesPerDirection = art->frameCount; art_ptr_unlock(cache_entry); if (frame >= framesPerDirection) { return -1; } if (rect != NULL) { obj_bound(obj, rect); obj->frame = frame; obj_bound(obj, &new_rect); rect_min_bound(rect, &new_rect, rect); } else { obj->frame = frame; } return 0; } // 0x48AAF0 int obj_inc_frame(Object* obj, Rect* dirtyRect) { Art* art; CacheEntry* cache_entry; int framesPerDirection; int nextFrame; if (obj == NULL) { return -1; } art = art_ptr_lock(obj->fid, &cache_entry); if (art == NULL) { return -1; } framesPerDirection = art->frameCount; art_ptr_unlock(cache_entry); nextFrame = obj->frame + 1; if (nextFrame >= framesPerDirection) { nextFrame = 0; } if (dirtyRect != NULL) { obj_bound(obj, dirtyRect); obj->frame = nextFrame; Rect updatedRect; obj_bound(obj, &updatedRect); rect_min_bound(dirtyRect, &updatedRect, dirtyRect); } else { obj->frame = nextFrame; } return 0; } // 0x48AB60 // int obj_dec_frame(Object* obj, Rect* dirtyRect) { Art* art; CacheEntry* cache_entry; int framesPerDirection; int prevFrame; Rect newRect; if (obj == NULL) { return -1; } art = art_ptr_lock(obj->fid, &cache_entry); if (art == NULL) { return -1; } framesPerDirection = art->frameCount; art_ptr_unlock(cache_entry); prevFrame = obj->frame - 1; if (prevFrame < 0) { prevFrame = framesPerDirection - 1; } if (dirtyRect != NULL) { obj_bound(obj, dirtyRect); obj->frame = prevFrame; obj_bound(obj, &newRect); rect_min_bound(dirtyRect, &newRect, dirtyRect); } else { obj->frame = prevFrame; } return 0; } // 0x48ABD4 int obj_set_rotation(Object* obj, int direction, Rect* dirtyRect) { if (obj == NULL) { return -1; } if (direction >= ROTATION_COUNT) { return -1; } if (dirtyRect != NULL) { obj_bound(obj, dirtyRect); obj->rotation = direction; Rect newRect; obj_bound(obj, &newRect); rect_min_bound(dirtyRect, &newRect, dirtyRect); } else { obj->rotation = direction; } return 0; } // 0x48AC20 int obj_inc_rotation(Object* obj, Rect* dirtyRect) { int rotation = obj->rotation + 1; if (rotation >= ROTATION_COUNT) { rotation = ROTATION_NE; } return obj_set_rotation(obj, rotation, dirtyRect); } // 0x48AC38 int obj_dec_rotation(Object* obj, Rect* dirtyRect) { int rotation = obj->rotation - 1; if (rotation < 0) { rotation = ROTATION_NW; } return obj_set_rotation(obj, rotation, dirtyRect); } // 0x48AC54 void obj_rebuild_all_light() { light_reset_tiles(); for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { obj_adjust_light(objectListNode->obj, 0, NULL); objectListNode = objectListNode->next; } } } // 0x48AC90 int obj_set_light(Object* obj, int lightDistance, int lightIntensity, Rect* rect) { int v7; Rect new_rect; if (obj == NULL) { return -1; } v7 = obj_turn_off_light(obj, rect); if (lightIntensity > 0) { if (lightDistance >= 8) { lightDistance = 8; } obj->lightIntensity = lightIntensity; obj->lightDistance = lightDistance; if (rect != NULL) { v7 = obj_turn_on_light(obj, &new_rect); rect_min_bound(rect, &new_rect, rect); } else { v7 = obj_turn_on_light(obj, NULL); } } else { obj->lightIntensity = 0; obj->lightDistance = 0; } return v7; } // 0x48AD04 int obj_get_visible_light(Object* obj) { int lightLevel = light_get_ambient(); int lightIntensity = light_get_tile_true(obj->elevation, obj->tile); if (obj == obj_dude) { lightIntensity -= obj_dude->lightIntensity; } if (lightIntensity >= lightLevel) { if (lightIntensity > LIGHT_LEVEL_MAX) { lightIntensity = LIGHT_LEVEL_MAX; } } else { lightIntensity = lightLevel; } return lightIntensity; } // 0x48AD48 int obj_turn_on_light(Object* obj, Rect* rect) { if (obj == NULL) { return -1; } if (obj->lightIntensity <= 0) { obj->flags &= ~OBJECT_LIGHTING; return -1; } if ((obj->flags & OBJECT_LIGHTING) == 0) { obj->flags |= OBJECT_LIGHTING; if (obj_adjust_light(obj, 0, rect) == -1) { if (rect != NULL) { obj_bound(obj, rect); } } } return 0; } // 0x48AD9C int obj_turn_off_light(Object* obj, Rect* rect) { if (obj == NULL) { return -1; } if (obj->lightIntensity <= 0) { obj->flags &= ~OBJECT_LIGHTING; return -1; } if ((obj->flags & OBJECT_LIGHTING) != 0) { if (obj_adjust_light(obj, 1, rect) == -1) { if (rect != NULL) { obj_bound(obj, rect); } } obj->flags &= ~OBJECT_LIGHTING; } return 0; } // 0x48ADF0 int obj_turn_on(Object* obj, Rect* rect) { if (obj == NULL) { return -1; } if ((obj->flags & OBJECT_HIDDEN) == 0) { return -1; } obj->flags &= ~OBJECT_HIDDEN; obj->outline &= ~OUTLINE_DISABLED; if (obj_adjust_light(obj, 0, rect) == -1) { if (rect != NULL) { obj_bound(obj, rect); } } if (obj == obj_dude) { if (rect != NULL) { Rect eggRect; obj_bound(obj_egg, &eggRect); rect_min_bound(rect, &eggRect, rect); } } return 0; } // 0x48AE68 int obj_turn_off(Object* object, Rect* rect) { if (object == NULL) { return -1; } if ((object->flags & OBJECT_HIDDEN) != 0) { return -1; } if (obj_adjust_light(object, 1, rect) == -1) { if (rect != NULL) { obj_bound(object, rect); } } object->flags |= OBJECT_HIDDEN; if ((object->outline & OUTLINE_TYPE_MASK) != 0) { object->outline |= OUTLINE_DISABLED; } if (object == obj_dude) { if (rect != NULL) { Rect eggRect; obj_bound(obj_egg, &eggRect); rect_min_bound(rect, &eggRect, rect); } } return 0; } // 0x48AEE4 int obj_turn_on_outline(Object* object, Rect* rect) { if (object == NULL) { return -1; } object->outline &= ~OUTLINE_DISABLED; if (rect != NULL) { obj_bound(object, rect); } return 0; } // 0x48AF00 int obj_turn_off_outline(Object* object, Rect* rect) { if (object == NULL) { return -1; } if ((object->outline & OUTLINE_TYPE_MASK) != 0) { object->outline |= OUTLINE_DISABLED; } if (rect != NULL) { obj_bound(object, rect); } return 0; } // 0x48AF2C int obj_toggle_flat(Object* object, Rect* rect) { Rect v1; if (object == NULL) { return -1; } ObjectListNode* node; ObjectListNode* previousNode; if (obj_node_ptr(object, &node, &previousNode) == -1) { return -1; } if (rect != NULL) { obj_bound(object, rect); if (previousNode != NULL) { previousNode->next = node->next; } else { int tile_index = node->obj->tile; if (tile_index == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile_index] = objectTable[tile_index]->next; } } object->flags ^= OBJECT_FLAT; obj_insert(node); obj_bound(object, &v1); rect_min_bound(rect, &v1, rect); } else { if (previousNode != NULL) { previousNode->next = node->next; } else { int tile = node->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } object->flags ^= OBJECT_FLAT; obj_insert(node); } return 0; } // 0x48B0FC int obj_erase_object(Object* object, Rect* rect) { if (object == NULL) { return -1; } gmouse_remove_item_outline(object); ObjectListNode* node; ObjectListNode* previousNode; if (obj_node_ptr(object, &node, &previousNode) == 0) { if (obj_adjust_light(object, 1, rect) == -1) { if (rect != NULL) { obj_bound(object, rect); } } if (obj_remove(node, previousNode) != 0) { return -1; } return 0; } // NOTE: Uninline. if (obj_create_object_node(&node) == -1) { return -1; } node->obj = object; if (obj_remove(node, node) == -1) { return -1; } return 0; } // 0x48B1B0 int obj_inven_free(Inventory* inventory) { for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); ObjectListNode* node; // NOTE: Uninline. obj_create_object_node(&node); node->obj = inventoryItem->item; node->obj->flags &= ~OBJECT_FLAG_0x400; obj_remove(node, node); inventoryItem->item = NULL; } if (inventory->items != NULL) { mem_free(inventory->items); inventory->items = NULL; inventory->capacity = 0; inventory->length = 0; } return 0; } // 0x48B24C bool obj_action_can_use(Object* obj) { int pid = obj->pid; if (pid != PROTO_ID_LIT_FLARE && pid != PROTO_ID_DYNAMITE_II && pid != PROTO_ID_PLASTIC_EXPLOSIVES_II) { return proto_action_can_use(pid); } else { return false; } } // 0x48B278 bool obj_action_can_talk_to(Object* obj) { return proto_action_can_talk_to(obj->pid) && (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) && critter_is_active(obj); } // 0x48B2A8 bool obj_portal_is_walk_thru(Object* obj) { if (PID_TYPE(obj->pid) != OBJ_TYPE_SCENERY) { return false; } Proto* proto; if (proto_ptr(obj->pid, &proto) == -1) { return false; } return (proto->scenery.data.generic.field_0 & 0x04) != 0; } // 0x48B2E8 Object* objFindObjPtrFromID(int a1) { Object* obj = obj_find_first(); while (obj != NULL) { if (obj->id == a1) { return obj; } obj = obj_find_next(); } return NULL; } // Returns root owner of given object. // // 0x48B304 Object* obj_top_environment(Object* object) { Object* owner = object->owner; if (owner == NULL) { return NULL; } while (owner->owner != NULL) { owner = owner->owner; } return owner; } // 0x48B318 void obj_remove_all() { ObjectListNode* node; ObjectListNode* prev; ObjectListNode* next; scr_remove_all(); for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { node = objectTable[tile]; prev = NULL; while (node != NULL) { next = node->next; if (obj_remove(node, prev) == -1) { prev = node; } node = next; } } node = floatingObjects; prev = NULL; while (node != NULL) { next = node->next; if (obj_remove(node, prev) == -1) { prev = node; } node = next; } obj_last_roof_y = -1; obj_last_elev = -1; obj_last_is_empty = true; obj_last_roof_x = -1; } // 0x48B3A8 Object* obj_find_first() { find_elev = 0; ObjectListNode* objectListNode; for (find_tile = 0; find_tile < HEX_GRID_SIZE; find_tile++) { objectListNode = objectTable[find_tile]; if (objectListNode) { break; } } if (find_tile == HEX_GRID_SIZE) { find_ptr = NULL; return NULL; } while (objectListNode != NULL) { if (art_get_disable(FID_TYPE(objectListNode->obj->fid)) == 0) { find_ptr = objectListNode; return objectListNode->obj; } objectListNode = objectListNode->next; } find_ptr = NULL; return NULL; } // 0x48B41C Object* obj_find_next() { if (find_ptr == NULL) { return NULL; } ObjectListNode* objectListNode = find_ptr->next; while (find_tile < HEX_GRID_SIZE) { if (objectListNode == NULL) { objectListNode = objectTable[find_tile++]; } while (objectListNode != NULL) { Object* object = objectListNode->obj; if (!art_get_disable(FID_TYPE(object->fid))) { find_ptr = objectListNode; return object; } objectListNode = objectListNode->next; } } find_ptr = NULL; return NULL; } // 0x48B48C Object* obj_find_first_at(int elevation) { find_elev = elevation; find_tile = 0; for (find_tile = 0; find_tile < HEX_GRID_SIZE; find_tile++) { ObjectListNode* objectListNode = objectTable[find_tile]; while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation == elevation) { if (!art_get_disable(FID_TYPE(object->fid))) { find_ptr = objectListNode; return object; } } objectListNode = objectListNode->next; } } find_ptr = NULL; return NULL; } // 0x48B510 Object* obj_find_next_at() { if (find_ptr == NULL) { return NULL; } ObjectListNode* objectListNode = find_ptr->next; while (find_tile < HEX_GRID_SIZE) { if (objectListNode == NULL) { objectListNode = objectTable[find_tile++]; } while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation == find_elev) { if (!art_get_disable(FID_TYPE(object->fid))) { find_ptr = objectListNode; return object; } } objectListNode = objectListNode->next; } } find_ptr = NULL; return NULL; } // 0x48B5A8 Object* obj_find_first_at_tile(int elevation, int tile) { find_elev = elevation; find_tile = tile; ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation == elevation) { if (!art_get_disable(FID_TYPE(object->fid))) { find_ptr = objectListNode; return object; } } objectListNode = objectListNode->next; } find_ptr = NULL; return NULL; } // 0x48B608 Object* obj_find_next_at_tile() { if (find_ptr == NULL) { return NULL; } ObjectListNode* objectListNode = find_ptr->next; while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation == find_elev) { if (!art_get_disable(FID_TYPE(object->fid))) { find_ptr = objectListNode; return object; } } objectListNode = objectListNode->next; } find_ptr = NULL; return NULL; } // 0x0x48B66C void obj_bound(Object* obj, Rect* rect) { if (obj == NULL) { return; } if (rect == NULL) { return; } bool isOutlined = false; if ((obj->outline & OUTLINE_TYPE_MASK) != 0) { isOutlined = true; } CacheEntry* artHandle; Art* art = art_ptr_lock(obj->fid, &artHandle); if (art == NULL) { rect->ulx = 0; rect->uly = 0; rect->lrx = 0; rect->lry = 0; return; } int width; int height; art_frame_width_length(art, obj->frame, obj->rotation, &width, &height); if (obj->tile == -1) { rect->ulx = obj->sx; rect->uly = obj->sy; rect->lrx = obj->sx + width - 1; rect->lry = obj->sy + height - 1; } else { int tileScreenY; int tileScreenX; if (tile_coord(obj->tile, &tileScreenX, &tileScreenY, obj->elevation) == 0) { tileScreenX += 16; tileScreenY += 8; tileScreenX += art->xOffsets[obj->rotation]; tileScreenY += art->yOffsets[obj->rotation]; tileScreenX += obj->x; tileScreenY += obj->y; rect->ulx = tileScreenX - width / 2; rect->uly = tileScreenY - height + 1; rect->lrx = width + rect->ulx - 1; rect->lry = tileScreenY; } else { rect->ulx = 0; rect->uly = 0; rect->lrx = 0; rect->lry = 0; isOutlined = false; } } art_ptr_unlock(artHandle); if (isOutlined) { rect->ulx--; rect->uly--; rect->lrx++; rect->lry++; } } // 0x48B7F8 bool obj_occupied(int tile, int elevation) { ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { if (objectListNode->obj->elevation == elevation && objectListNode->obj != obj_mouse && objectListNode->obj != obj_mouse_flat) { return true; } objectListNode = objectListNode->next; } return false; } // 0x48B848 Object* obj_blocking_at(Object* a1, int tile, int elev) { ObjectListNode* objectListNode; Object* v7; int type; if (!hexGridTileIsValid(tile)) { return NULL; } objectListNode = objectTable[tile]; while (objectListNode != NULL) { v7 = objectListNode->obj; if (v7->elevation == elev) { if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) { type = FID_TYPE(v7->fid); if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { return v7; } } } objectListNode = objectListNode->next; } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int neighboor = tile_num_in_direction(tile, rotation, 1); if (hexGridTileIsValid(neighboor)) { objectListNode = objectTable[neighboor]; while (objectListNode != NULL) { v7 = objectListNode->obj; if ((v7->flags & OBJECT_MULTIHEX) != 0) { if (v7->elevation == elev) { if ((v7->flags & OBJECT_HIDDEN) == 0 && (v7->flags & OBJECT_NO_BLOCK) == 0 && v7 != a1) { type = FID_TYPE(v7->fid); if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { return v7; } } } } objectListNode = objectListNode->next; } } } return NULL; } // 0x48B930 Object* obj_shoot_blocking_at(Object* obj, int tile, int elev) { if (!hexGridTileIsValid(tile)) { return NULL; } ObjectListNode* objectListItem = objectTable[tile]; while (objectListItem != NULL) { Object* candidate = objectListItem->obj; if (candidate->elevation == elev) { unsigned int flags = candidate->flags; if ((flags & OBJECT_HIDDEN) == 0 && ((flags & OBJECT_NO_BLOCK) == 0 || (flags & OBJECT_SHOOT_THRU) == 0) && candidate != obj) { int type = FID_TYPE(candidate->fid); if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { return candidate; } } } objectListItem = objectListItem->next; } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int adjacentTile = tile_num_in_direction(tile, rotation, 1); if (!hexGridTileIsValid(adjacentTile)) { continue; } ObjectListNode* objectListItem = objectTable[adjacentTile]; while (objectListItem != NULL) { Object* candidate = objectListItem->obj; unsigned int flags = candidate->flags; if ((flags & OBJECT_MULTIHEX) != 0) { if (candidate->elevation == elev) { if ((flags & OBJECT_HIDDEN) == 0 && (flags & OBJECT_NO_BLOCK) == 0 && candidate != obj) { int type = FID_TYPE(candidate->fid); if (type == OBJ_TYPE_CRITTER || type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { return candidate; } } } } objectListItem = objectListItem->next; } } return NULL; } // 0x48BA20 Object* obj_ai_blocking_at(Object* a1, int tile, int elevation) { if (!hexGridTileIsValid(tile)) { return NULL; } ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation == elevation) { if ((object->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_NO_BLOCK) == 0 && object != a1) { int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_CRITTER || objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) { if (moveBlockObj != NULL || objectType != OBJ_TYPE_CRITTER) { return object; } moveBlockObj = object; } } } objectListNode = objectListNode->next; } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int candidate = tile_num_in_direction(tile, rotation, 1); if (!hexGridTileIsValid(candidate)) { continue; } objectListNode = objectTable[candidate]; while (objectListNode != NULL) { Object* object = objectListNode->obj; if ((object->flags & OBJECT_MULTIHEX) != 0) { if (object->elevation == elevation) { if ((object->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_NO_BLOCK) == 0 && object != a1) { int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_CRITTER || objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) { if (moveBlockObj != NULL || objectType != OBJ_TYPE_CRITTER) { return object; } moveBlockObj = object; } } } } objectListNode = objectListNode->next; } } return NULL; } // 0x48BB44 int obj_scroll_blocking_at(int tile, int elev) { // TODO: Might be an error - why tile 0 is excluded? if (tile <= 0 || tile >= 40000) { return -1; } ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { if (elev < objectListNode->obj->elevation) { break; } if (objectListNode->obj->elevation == elev && objectListNode->obj->pid == 0x500000C) { return 0; } objectListNode = objectListNode->next; } return -1; } // 0x48BB88 Object* obj_sight_blocking_at(Object* a1, int tile, int elevation) { ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation == elevation && (object->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_LIGHT_THRU) == 0 && object != a1) { int objectType = FID_TYPE(object->fid); if (objectType == OBJ_TYPE_SCENERY || objectType == OBJ_TYPE_WALL) { return object; } } objectListNode = objectListNode->next; } return NULL; } // 0x48BBD4 int obj_dist(Object* object1, Object* object2) { if (object1 == NULL || object2 == NULL) { return 0; } int distance = tile_dist(object1->tile, object2->tile); if ((object1->flags & OBJECT_MULTIHEX) != 0) { distance -= 1; } if ((object2->flags & OBJECT_MULTIHEX) != 0) { distance -= 1; } if (distance < 0) { distance = 0; } return distance; } // 0x48BC08 int obj_dist_with_tile(Object* object1, int tile1, Object* object2, int tile2) { if (object1 == NULL || object2 == NULL) { return 0; } int distance = tile_dist(tile1, tile2); if ((object1->flags & OBJECT_MULTIHEX) != 0) { distance -= 1; } if ((object2->flags & OBJECT_MULTIHEX) != 0) { distance -= 1; } if (distance < 0) { distance = 0; } return distance; } // 0x48BC38 int obj_create_list(int tile, int elevation, int objectType, Object*** objectListPtr) { if (objectListPtr == NULL) { return -1; } int count = 0; if (tile == -1) { for (int index = 0; index < HEX_GRID_SIZE; index++) { ObjectListNode* objectListNode = objectTable[index]; while (objectListNode != NULL) { Object* obj = objectListNode->obj; if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->elevation == elevation && FID_TYPE(obj->fid) == objectType) { count++; } objectListNode = objectListNode->next; } } } else { ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { Object* obj = objectListNode->obj; if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->elevation == elevation && FID_TYPE(objectListNode->obj->fid) == objectType) { count++; } objectListNode = objectListNode->next; } } if (count == 0) { return 0; } Object** objects = *objectListPtr = (Object**)mem_malloc(sizeof(*objects) * count); if (objects == NULL) { return -1; } if (tile == -1) { for (int index = 0; index < HEX_GRID_SIZE; index++) { ObjectListNode* objectListNode = objectTable[index]; while (objectListNode) { Object* obj = objectListNode->obj; if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->elevation == elevation && FID_TYPE(obj->fid) == objectType) { *objects++ = obj; } objectListNode = objectListNode->next; } } } else { ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { Object* obj = objectListNode->obj; if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->elevation == elevation && FID_TYPE(obj->fid) == objectType) { *objects++ = obj; } objectListNode = objectListNode->next; } } return count; } // 0x48BDCC void obj_delete_list(Object** objectList) { if (objectList != NULL) { mem_free(objectList); } } // 0x48BDD8 void translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, unsigned char* a9, unsigned char* a10) { dest += destPitch * destY + destX; int srcStep = srcPitch - srcWidth; int destStep = destPitch - srcWidth; for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { // TODO: Probably wrong. unsigned char v1 = a10[*src]; unsigned char* v2 = a9 + (v1 << 8); unsigned char v3 = *dest; *dest = v2[v3]; src++; dest++; } src += srcStep; dest += destStep; } } // 0x48BEFC void dark_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light) { unsigned char* sp = src; unsigned char* dp = dest + destPitch * destY + destX; int srcStep = srcPitch - srcWidth; int destStep = destPitch - srcWidth; // TODO: Name might be confusing. int lightModifier = light >> 9; for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { unsigned char b = *sp; if (b != 0) { if (b < 0xE5) { b = intensityColorTable[b][lightModifier]; } *dp = b; } sp++; dp++; } sp += srcStep; dp += destStep; } } // 0x48BF88 void dark_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light, unsigned char* a10, unsigned char* a11) { int srcStep = srcPitch - srcWidth; int destStep = destPitch - srcWidth; int lightModifier = light >> 9; dest += destPitch * destY + destX; for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { unsigned char srcByte = *src; if (srcByte != 0) { unsigned char destByte = *dest; unsigned int index = a11[srcByte] << 8; index = a10[index + destByte]; *dest = intensityColorTable[index][lightModifier]; } src++; dest++; } src += srcStep; dest += destStep; } } // 0x48C03C void intensity_mask_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destPitch, unsigned char* mask, int maskPitch, int light) { int srcStep = srcPitch - srcWidth; int destStep = destPitch - srcWidth; int maskStep = maskPitch - srcWidth; light >>= 9; for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { unsigned char b = *src; if (b != 0) { b = intensityColorTable[b][light]; unsigned char m = *mask; if (m != 0) { unsigned char d = *dest; int q = intensityColorTable[d][128 - m]; m = intensityColorTable[b][m]; b = colorMixAddTable[m][q]; } *dest = b; } src++; dest++; mask++; } src += srcStep; dest += destStep; mask += maskStep; } } // 0x48C2B4 int obj_outline_object(Object* obj, int outlineType, Rect* rect) { if (obj == NULL) { return -1; } if ((obj->outline & OUTLINE_TYPE_MASK) != 0) { return -1; } if ((obj->flags & OBJECT_NO_HIGHLIGHT) != 0) { return -1; } obj->outline = outlineType; if ((obj->flags & OBJECT_HIDDEN) != 0) { obj->outline |= OUTLINE_DISABLED; } if (rect != NULL) { obj_bound(obj, rect); } return 0; } // 0x48C2F0 int obj_remove_outline(Object* object, Rect* rect) { if (object == NULL) { return -1; } if (rect != NULL) { obj_bound(object, rect); } object->outline = 0; return 0; } // 0x48C340 int obj_intersects_with(Object* object, int x, int y) { int flags = 0; if (object == obj_egg || (object->flags & OBJECT_HIDDEN) == 0) { CacheEntry* handle; Art* art = art_ptr_lock(object->fid, &handle); if (art != NULL) { int width; int height; art_frame_width_length(art, object->frame, object->rotation, &width, &height); int minX; int minY; int maxX; int maxY; if (object->tile == -1) { minX = object->sx; minY = object->sy; maxX = minX + width - 1; maxY = minY + height - 1; } else { int tileScreenX; int tileScreenY; tile_coord(object->tile, &tileScreenX, &tileScreenY, object->elevation); tileScreenX += 16; tileScreenY += 8; tileScreenX += art->xOffsets[object->rotation]; tileScreenY += art->yOffsets[object->rotation]; tileScreenX += object->x; tileScreenY += object->y; minX = tileScreenX - width / 2; maxX = minX + width - 1; minY = tileScreenY - height + 1; maxY = tileScreenY; } if (x >= minX && x <= maxX && y >= minY && y <= maxY) { unsigned char* data = art_frame_data(art, object->frame, object->rotation); if (data != NULL) { if (data[width * (y - minY) + x - minX] != 0) { flags |= 0x01; if ((object->flags & OBJECT_FLAG_0xFC000) != 0) { if ((object->flags & OBJECT_TRANS_NONE) == 0) { flags &= ~0x03; flags |= 0x02; } } else { int type = FID_TYPE(object->fid); if (type == OBJ_TYPE_SCENERY || type == OBJ_TYPE_WALL) { Proto* proto; proto_ptr(object->pid, &proto); bool v20; int extendedFlags = proto->scenery.extendedFlags; if ((extendedFlags & 0x8000000) != 0 || (extendedFlags & 0x80000000) != 0) { v20 = tile_in_front_of(object->tile, obj_dude->tile); } else if ((extendedFlags & 0x10000000) != 0) { // NOTE: Original code uses bitwise or, but given the fact that these functions return // bools, logical or is more suitable. v20 = tile_in_front_of(object->tile, obj_dude->tile) || tile_to_right_of(obj_dude->tile, object->tile); } else if ((extendedFlags & 0x20000000) != 0) { v20 = tile_in_front_of(object->tile, obj_dude->tile) && tile_to_right_of(obj_dude->tile, object->tile); } else { v20 = tile_to_right_of(obj_dude->tile, object->tile); } if (v20) { if (obj_intersects_with(obj_egg, x, y) != 0) { flags |= 0x04; } } } } } } } art_ptr_unlock(handle); } } return flags; } // 0x48C5C4 int obj_create_intersect_list(int x, int y, int elevation, int objectType, ObjectWithFlags** entriesPtr) { int v5 = tile_num(x - 320, y - 240, elevation); *entriesPtr = NULL; if (updateHexArea <= 0) { return 0; } int count = 0; int parity = tile_center_tile & 1; for (int index = 0; index < updateHexArea; index++) { int v7 = orderTable[parity][index]; if (offsetDivTable[v7] < 30 && offsetModTable[v7] < 20) { ObjectListNode* objectListNode = objectTable[offsetTable[parity][v7] + v5]; while (objectListNode != NULL) { Object* object = objectListNode->obj; if (object->elevation > elevation) { break; } if (object->elevation == elevation && (objectType == -1 || FID_TYPE(object->fid) == objectType) && object != obj_egg) { int flags = obj_intersects_with(object, x, y); if (flags != 0) { ObjectWithFlags* entries = (ObjectWithFlags*)mem_realloc(*entriesPtr, sizeof(*entries) * (count + 1)); if (entries != NULL) { *entriesPtr = entries; entries[count].object = object; entries[count].flags = flags; count++; } } } objectListNode = objectListNode->next; } } } return count; } // 0x48C74C void obj_delete_intersect_list(ObjectWithFlags** entriesPtr) { if (entriesPtr != NULL && *entriesPtr != NULL) { mem_free(*entriesPtr); *entriesPtr = NULL; } } // NOTE: Inlined. // // 0x48C76C void obj_set_seen(int tile) { obj_seen[tile >> 3] |= 1 << (tile & 7); } // 0x48C788 void obj_clear_seen() { memset(obj_seen, 0, sizeof(obj_seen)); } // 0x48C7A0 void obj_process_seen() { int i; int v7; int v8; int v5; int v0; int v3; ObjectListNode* obj_entry; memset(obj_seen_check, 0, 5001); v0 = 400; for (i = 0; i < 5001; i++) { if (obj_seen[i] != 0) { for (v3 = i - 400; v3 != v0; v3 += 25) { if (v3 >= 0 && v3 < 5001) { obj_seen_check[v3] = -1; if (v3 > 0) { obj_seen_check[v3 - 1] = -1; } if (v3 < 5000) { obj_seen_check[v3 + 1] = -1; } if (v3 > 1) { obj_seen_check[v3 - 2] = -1; } if (v3 < 4999) { obj_seen_check[v3 + 2] = -1; } } } } v0++; } v7 = 0; for (i = 0; i < 5001; i++) { if (obj_seen_check[i] != 0) { v8 = 1; for (v5 = v7; v5 < v7 + 8; v5++) { if (v8 & obj_seen_check[i]) { if (v5 < 40000) { for (obj_entry = objectTable[v5]; obj_entry != NULL; obj_entry = obj_entry->next) { if (obj_entry->obj->elevation == obj_dude->elevation) { obj_entry->obj->flags |= OBJECT_SEEN; } } } } v8 *= 2; } } v7 += 8; } memset(obj_seen, 0, 5001); } // 0x48C8E4 char* object_name(Object* obj) { int objectType = FID_TYPE(obj->fid); switch (objectType) { case OBJ_TYPE_ITEM: return item_name(obj); case OBJ_TYPE_CRITTER: return critter_name(obj); default: return proto_name(obj->pid); } } // 0x48C914 char* object_description(Object* obj) { if (FID_TYPE(obj->fid) == OBJ_TYPE_ITEM) { return item_description(obj); } return proto_description(obj->pid); } // Warm objects cache? // // 0x48C938 void obj_preload_art_cache(int flags) { if (preload_list == NULL) { return; } unsigned char arr[4096]; memset(arr, 0, sizeof(arr)); if ((flags & 0x02) == 0) { for (int i = 0; i < SQUARE_GRID_SIZE; i++) { int v3 = square[0]->field_0[i]; arr[v3 & 0xFFF] = 1; arr[(v3 >> 16) & 0xFFF] = 1; } } if ((flags & 0x04) == 0) { for (int i = 0; i < SQUARE_GRID_SIZE; i++) { int v3 = square[1]->field_0[i]; arr[v3 & 0xFFF] = 1; arr[(v3 >> 16) & 0xFFF] = 1; } } if ((flags & 0x08) == 0) { for (int i = 0; i < SQUARE_GRID_SIZE; i++) { int v3 = square[2]->field_0[i]; arr[v3 & 0xFFF] = 1; arr[(v3 >> 16) & 0xFFF] = 1; } } qsort(preload_list, preload_list_index, sizeof(*preload_list), obj_preload_sort); int v11 = preload_list_index; int v12 = preload_list_index; if (FID_TYPE(preload_list[v12 - 1]) == OBJ_TYPE_WALL) { int objectType = OBJ_TYPE_ITEM; do { v11--; objectType = FID_TYPE(preload_list[v12 - 1]); v12--; } while (objectType == OBJ_TYPE_WALL); v11++; } CacheEntry* cache_handle; if (art_ptr_lock(*preload_list, &cache_handle) != NULL) { art_ptr_unlock(cache_handle); } for (int i = 1; i < v11; i++) { if (preload_list[i - 1] != preload_list[i]) { if (art_ptr_lock(preload_list[i], &cache_handle) != NULL) { art_ptr_unlock(cache_handle); } } } for (int i = 0; i < 4096; i++) { if (arr[i] != 0) { int fid = art_id(OBJ_TYPE_TILE, i, 0, 0, 0); if (art_ptr_lock(fid, &cache_handle) != NULL) { art_ptr_unlock(cache_handle); } } } for (int i = v11; i < preload_list_index; i++) { if (preload_list[i - 1] != preload_list[i]) { if (art_ptr_lock(preload_list[i], &cache_handle) != NULL) { art_ptr_unlock(cache_handle); } } } mem_free(preload_list); preload_list = NULL; preload_list_index = 0; } // 0x48CB88 static int obj_offset_table_init() { int i; if (offsetTable[0] != NULL) { return -1; } if (offsetTable[1] != NULL) { return -1; } offsetTable[0] = (int*)mem_malloc(sizeof(int) * updateHexArea); if (offsetTable[0] == NULL) { goto err; } offsetTable[1] = (int*)mem_malloc(sizeof(int) * updateHexArea); if (offsetTable[1] == NULL) { goto err; } for (int parity = 0; parity < 2; parity++) { int originTile = tile_num(updateAreaPixelBounds.ulx, updateAreaPixelBounds.uly, 0); if (originTile != -1) { int* offsets = offsetTable[tile_center_tile & 1]; int originTileX; int originTileY; tile_coord(originTile, &originTileX, &originTileY, 0); int parityShift = 16; originTileX += 16; originTileY += 8; if (originTileX > updateAreaPixelBounds.ulx) { parityShift = -parityShift; } int tileX = originTileX; for (int y = 0; y < updateHexHeight; y++) { for (int x = 0; x < updateHexWidth; x++) { int tile = tile_num(tileX, originTileY, 0); if (tile == -1) { goto err; } tileX += 32; *offsets++ = tile - originTile; } tileX = parityShift + originTileX; originTileY += 12; parityShift = -parityShift; } } if (tile_set_center(tile_center_tile + 1, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == -1) { goto err; } } offsetDivTable = (int*)mem_malloc(sizeof(int) * updateHexArea); if (offsetDivTable == NULL) { goto err; } for (i = 0; i < updateHexArea; i++) { offsetDivTable[i] = i / updateHexWidth; } offsetModTable = (int*)mem_malloc(sizeof(int) * updateHexArea); if (offsetModTable == NULL) { goto err; } for (i = 0; i < updateHexArea; i++) { offsetModTable[i] = i % updateHexWidth; } return 0; err: obj_offset_table_exit(); return -1; } // 0x48CDA0 static void obj_offset_table_exit() { if (offsetModTable != NULL) { mem_free(offsetModTable); offsetModTable = NULL; } if (offsetDivTable != NULL) { mem_free(offsetDivTable); offsetDivTable = NULL; } if (offsetTable[1] != NULL) { mem_free(offsetTable[1]); offsetTable[1] = NULL; } if (offsetTable[0] != NULL) { mem_free(offsetTable[0]); offsetTable[0] = NULL; } } // 0x48CE10 static int obj_order_table_init() { if (orderTable[0] != NULL || orderTable[1] != NULL) { return -1; } orderTable[0] = (int*)mem_malloc(sizeof(int) * updateHexArea); if (orderTable[0] == NULL) { goto err; } orderTable[1] = (int*)mem_malloc(sizeof(int) * updateHexArea); if (orderTable[1] == NULL) { goto err; } for (int index = 0; index < updateHexArea; index++) { orderTable[0][index] = index; orderTable[1][index] = index; } qsort(orderTable[0], updateHexArea, sizeof(int), obj_order_comp_func_even); qsort(orderTable[1], updateHexArea, sizeof(int), obj_order_comp_func_odd); return 0; err: // NOTE: Uninline. obj_order_table_exit(); return -1; } // 0x48CF20 static int obj_order_comp_func_even(const void* a1, const void* a2) { int v1 = *(int*)a1; int v2 = *(int*)a2; return offsetTable[0][v1] - offsetTable[0][v2]; } // 0x48CF38 static int obj_order_comp_func_odd(const void* a1, const void* a2) { int v1 = *(int*)a1; int v2 = *(int*)a2; return offsetTable[1][v1] - offsetTable[1][v2]; } // NOTE: Inlined. // // 0x48CF50 static void obj_order_table_exit() { if (orderTable[1] != NULL) { mem_free(orderTable[1]); orderTable[1] = NULL; } if (orderTable[0] != NULL) { mem_free(orderTable[0]); orderTable[0] = NULL; } } // 0x48CF8C static int obj_render_table_init() { if (renderTable != NULL) { return -1; } renderTable = (ObjectListNode**)mem_malloc(sizeof(*renderTable) * updateHexArea); if (renderTable == NULL) { return -1; } for (int index = 0; index < updateHexArea; index++) { renderTable[index] = NULL; } return 0; } // NOTE: Inlined. // // 0x48D000 static void obj_render_table_exit() { if (renderTable != NULL) { mem_free(renderTable); renderTable = NULL; } } // 0x48D020 static void obj_light_table_init() { for (int s = 0; s < 2; s++) { int v4 = tile_center_tile + s; for (int i = 0; i < ROTATION_COUNT; i++) { int v15 = 8; int* p = light_offsets[v4 & 1][i]; for (int j = 0; j < 8; j++) { int tile = tile_num_in_direction(v4, (i + 1) % ROTATION_COUNT, j); for (int m = 0; m < v15; m++) { *p++ = tile_num_in_direction(tile, i, m + 1) - v4; } v15--; } } } } // 0x48D1E4 static void obj_blend_table_init() { for (int index = 0; index < 256; index++) { int r = (Color2RGB(index) & 0x7C00) >> 10; int g = (Color2RGB(index) & 0x3E0) >> 5; int b = Color2RGB(index) & 0x1F; glassGrayTable[index] = ((r + 5 * g + 4 * b) / 10) >> 2; commonGrayTable[index] = ((b + 3 * r + 6 * g) / 10) >> 2; } glassGrayTable[0] = 0; commonGrayTable[0] = 0; wallBlendTable = getColorBlendTable(colorTable[25439]); glassBlendTable = getColorBlendTable(colorTable[10239]); steamBlendTable = getColorBlendTable(colorTable[32767]); energyBlendTable = getColorBlendTable(colorTable[30689]); redBlendTable = getColorBlendTable(colorTable[31744]); } // NOTE: Inlined. // // 0x48D2E8 static void obj_blend_table_exit() { freeColorBlendTable(colorTable[25439]); freeColorBlendTable(colorTable[10239]); freeColorBlendTable(colorTable[32767]); freeColorBlendTable(colorTable[30689]); freeColorBlendTable(colorTable[31744]); } // 0x48D348 int obj_save_obj(File* stream, Object* object) { if ((object->flags & OBJECT_TEMPORARY) != 0) { return 0; } CritterCombatData* combatData = NULL; Object* whoHitMe = NULL; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combatData = &(object->data.critter.combat); whoHitMe = combatData->whoHitMe; if (whoHitMe != 0) { if (combatData->whoHitMeCid != -1) { combatData->whoHitMeCid = whoHitMe->cid; } } else { combatData->whoHitMeCid = -1; } } if (obj_write_obj(object, stream) == -1) { return -1; } if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combatData->whoHitMe = whoHitMe; } Inventory* inventory = &(object->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (db_fwriteInt(stream, inventoryItem->quantity) == -1) { return -1; } if (obj_save_obj(stream, inventoryItem->item) == -1) { return -1; } if ((inventoryItem->item->flags & OBJECT_TEMPORARY) != 0) { return -1; } } return 0; } // 0x48D414 int obj_load_obj(File* stream, Object** objectPtr, int elevation, Object* owner) { Object* obj; if (obj_create_object(&obj) == -1) { *objectPtr = NULL; return -1; } if (obj_read_obj(obj, stream) != 0) { *objectPtr = NULL; return -1; } if (obj->sid != -1) { Script* script; if (scr_ptr(obj->sid, &script) == -1) { obj->sid = -1; } else { script->owner = obj; } } obj_fix_violence_settings(&(obj->fid)); if (!art_fid_valid(obj->fid)) { debug_printf("\nError: invalid object art fid: %u\n", obj->fid); // NOTE: Uninline. obj_destroy_object(&obj); return -2; } if (elevation == -1) { elevation = obj->elevation; } else { obj->elevation = elevation; } obj->owner = owner; Inventory* inventory = &(obj->data.inventory); if (inventory->length <= 0) { inventory->capacity = 0; inventory->items = NULL; *objectPtr = obj; return 0; } InventoryItem* inventoryItems = inventory->items = (InventoryItem*)mem_malloc(sizeof(*inventoryItems) * inventory->capacity); if (inventoryItems == NULL) { return -1; } for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) { InventoryItem* inventoryItem = &(inventoryItems[inventoryItemIndex]); if (db_freadInt(stream, &(inventoryItem->quantity)) != 0) { return -1; } if (obj_load_obj(stream, &(inventoryItem->item), elevation, obj) != 0) { return -1; } } *objectPtr = obj; return 0; } // obj_save_dude // 0x48D59C int obj_save_dude(File* stream) { int field_78 = obj_dude->sid; obj_dude->flags &= ~OBJECT_TEMPORARY; obj_dude->sid = -1; int rc = obj_save_obj(stream, obj_dude); obj_dude->sid = field_78; obj_dude->flags |= OBJECT_TEMPORARY; if (db_fwriteInt(stream, tile_center_tile) == -1) { db_fclose(stream); return -1; } return rc; } // obj_load_dude // 0x48D600 int obj_load_dude(File* stream) { int savedTile = obj_dude->tile; int savedElevation = obj_dude->elevation; int savedRotation = obj_dude->rotation; int savedOid = obj_dude->id; scr_clear_dude_script(); Object* temp; int rc = obj_load_obj(stream, &temp, -1, NULL); memcpy(obj_dude, temp, sizeof(*obj_dude)); obj_dude->flags |= OBJECT_TEMPORARY; scr_clear_dude_script(); obj_dude->id = savedOid; scr_set_dude_script(); int newTile = obj_dude->tile; obj_dude->tile = savedTile; int newElevation = obj_dude->elevation; obj_dude->elevation = savedElevation; int newRotation = obj_dude->rotation; obj_dude->rotation = newRotation; scr_set_dude_script(); if (rc != -1) { obj_move_to_tile(obj_dude, newTile, newElevation, NULL); obj_set_rotation(obj_dude, newRotation, NULL); } // Set ownership of inventory items from temporary instance to dude. Inventory* inventory = &(obj_dude->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); inventoryItem->item->owner = obj_dude; } obj_fix_combat_cid_for_dude(); // Dude has claimed ownership of items in temporary instance's inventory. // We don't need object's dealloc routine to remove these items from the // game, so simply nullify temporary inventory as if nothing was there. Inventory* tempInventory = &(temp->data.inventory); tempInventory->length = 0; tempInventory->capacity = 0; tempInventory->items = NULL; temp->flags &= ~OBJECT_FLAG_0x400; if (obj_erase_object(temp, NULL) == -1) { debug_printf("\nError: obj_load_dude: Can't destroy temp object!\n"); } inven_reset_dude(); int tile; if (db_freadInt(stream, &tile) == -1) { db_fclose(stream); return -1; } tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS); return rc; } // 0x48D778 static int obj_create_object(Object** objectPtr) { if (objectPtr == NULL) { return -1; } Object* object = *objectPtr = (Object*)mem_malloc(sizeof(Object)); if (object == NULL) { return -1; } memset(object, 0, sizeof(Object)); object->id = -1; object->tile = -1; object->cid = -1; object->outline = 0; object->pid = -1; object->sid = -1; object->owner = NULL; object->field_80 = -1; return 0; } // NOTE: Inlined. // // 0x48D7F8 static void obj_destroy_object(Object** objectPtr) { if (objectPtr == NULL) { return; } if (*objectPtr == NULL) { return; } mem_free(*objectPtr); *objectPtr = NULL; } // NOTE: Inlined. // // 0x48D818 static int obj_create_object_node(ObjectListNode** nodePtr) { if (nodePtr == NULL) { return -1; } ObjectListNode* node = *nodePtr = (ObjectListNode*)mem_malloc(sizeof(*node)); if (node == NULL) { return -1; } node->obj = NULL; node->next = NULL; return 0; } // NOTE: Inlined. // // 0x48D84C static void obj_destroy_object_node(ObjectListNode** nodePtr) { if (nodePtr == NULL) { return; } if (*nodePtr == NULL) { return; } mem_free(*nodePtr); *nodePtr = NULL; } // 0x48D86C static int obj_node_ptr(Object* object, ObjectListNode** nodePtr, ObjectListNode** previousNodePtr) { if (object == NULL) { return -1; } if (nodePtr == NULL) { return -1; } int tile = object->tile; if (tile != -1) { *nodePtr = objectTable[tile]; } else { *nodePtr = floatingObjects; } if (previousNodePtr != NULL) { *previousNodePtr = NULL; while (*nodePtr != NULL) { if (object == (*nodePtr)->obj) { break; } *previousNodePtr = *nodePtr; *nodePtr = (*nodePtr)->next; } } else { while (*nodePtr != NULL) { if (object == (*nodePtr)->obj) { break; } *nodePtr = (*nodePtr)->next; } } if (*nodePtr != NULL) { return 0; } return -1; } // 0x48D8E8 static void obj_insert(ObjectListNode* objectListNode) { ObjectListNode** objectListNodePtr; if (objectListNode == NULL) { return; } if (objectListNode->obj->tile == -1) { objectListNodePtr = &floatingObjects; } else { Art* art = NULL; CacheEntry* cacheHandle = NULL; objectListNodePtr = &(objectTable[objectListNode->obj->tile]); while (*objectListNodePtr != NULL) { Object* obj = (*objectListNodePtr)->obj; if (obj->elevation > objectListNode->obj->elevation) { break; } if (obj->elevation == objectListNode->obj->elevation) { if ((obj->flags & OBJECT_FLAT) == 0 && (objectListNode->obj->flags & OBJECT_FLAT) != 0) { break; } if ((obj->flags & OBJECT_FLAT) == (objectListNode->obj->flags & OBJECT_FLAT)) { bool v11 = false; CacheEntry* a2; Art* v12 = art_ptr_lock(obj->fid, &a2); if (v12 != NULL) { if (art == NULL) { art = art_ptr_lock(objectListNode->obj->fid, &cacheHandle); } // TODO: Incomplete. art_ptr_unlock(a2); if (v11) { break; } } } } objectListNodePtr = &((*objectListNodePtr)->next); } if (art != NULL) { art_ptr_unlock(cacheHandle); } } objectListNode->next = *objectListNodePtr; *objectListNodePtr = objectListNode; } // 0x48DA58 static int obj_remove(ObjectListNode* a1, ObjectListNode* a2) { if (a1->obj == NULL) { return -1; } if ((a1->obj->flags & OBJECT_FLAG_0x400) != 0) { return -1; } obj_inven_free(&(a1->obj->data.inventory)); if (a1->obj->sid != -1) { exec_script_proc(a1->obj->sid, SCRIPT_PROC_DESTROY); scr_remove(a1->obj->sid); } if (a1 != a2) { if (a2 != NULL) { a2->next = a1->next; } else { int tile = a1->obj->tile; if (tile == -1) { floatingObjects = floatingObjects->next; } else { objectTable[tile] = objectTable[tile]->next; } } } // NOTE: Uninline. obj_destroy_object(&(a1->obj)); // NOTE: Uninline. obj_destroy_object_node(&a1); return 0; } // 0x48DB28 static int obj_connect_to_tile(ObjectListNode* node, int tile, int elevation, Rect* rect) { if (node == NULL) { return -1; } if (!hexGridTileIsValid(tile)) { return -1; } if (!elevationIsValid(elevation)) { return -1; } node->obj->tile = tile; node->obj->elevation = elevation; node->obj->x = 0; node->obj->y = 0; node->obj->owner = 0; obj_insert(node); if (obj_adjust_light(node->obj, 0, rect) == -1) { if (rect != NULL) { obj_bound(node->obj, rect); } } return 0; } // 0x48DC28 static int obj_adjust_light(Object* obj, int a2, Rect* rect) { if (obj == NULL) { return -1; } if (obj->lightIntensity <= 0) { return -1; } if ((obj->flags & OBJECT_HIDDEN) != 0) { return -1; } if ((obj->flags & OBJECT_LIGHTING) == 0) { return -1; } if (!hexGridTileIsValid(obj->tile)) { return -1; } AdjustLightIntensityProc* adjustLightIntensity = a2 ? light_subtract_from_tile : light_add_to_tile; adjustLightIntensity(obj->elevation, obj->tile, obj->lightIntensity); Rect objectRect; obj_bound(obj, &objectRect); if (obj->lightDistance > 8) { obj->lightDistance = 8; } if (obj->lightIntensity > 65536) { obj->lightIntensity = 65536; } int(*v70)[36] = light_offsets[obj->tile & 1]; int v7 = (obj->lightIntensity - 655) / (obj->lightDistance + 1); int v28[36]; v28[0] = obj->lightIntensity - v7; v28[1] = v28[0] - v7; v28[8] = v28[0] - v7; v28[2] = v28[0] - v7 - v7; v28[9] = v28[2]; v28[15] = v28[0] - v7 - v7; v28[3] = v28[2] - v7; v28[10] = v28[2] - v7; v28[16] = v28[2] - v7; v28[21] = v28[2] - v7; v28[4] = v28[2] - v7 - v7; v28[11] = v28[4]; v28[17] = v28[2] - v7 - v7; v28[22] = v28[2] - v7 - v7; v28[26] = v28[2] - v7 - v7; v28[5] = v28[4] - v7; v28[12] = v28[4] - v7; v28[18] = v28[4] - v7; v28[23] = v28[4] - v7; v28[27] = v28[4] - v7; v28[30] = v28[4] - v7; v28[6] = v28[4] - v7 - v7; v28[13] = v28[6]; v28[19] = v28[4] - v7 - v7; v28[24] = v28[4] - v7 - v7; v28[28] = v28[4] - v7 - v7; v28[31] = v28[4] - v7 - v7; v28[33] = v28[4] - v7 - v7; v28[7] = v28[6] - v7; v28[14] = v28[6] - v7; v28[20] = v28[6] - v7; v28[25] = v28[6] - v7; v28[29] = v28[6] - v7; v28[32] = v28[6] - v7; v28[34] = v28[6] - v7; v28[35] = v28[6] - v7; for (int index = 0; index < 36; index++) { if (obj->lightDistance >= light_distance[index]) { for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int v14; int nextRotation = (rotation + 1) % ROTATION_COUNT; int eax; int edx; int ebx; int esi; int edi; switch (index) { case 0: v14 = 0; break; case 1: v14 = light_blocked[rotation][0]; break; case 2: v14 = light_blocked[rotation][1]; break; case 3: v14 = light_blocked[rotation][2]; break; case 4: v14 = light_blocked[rotation][3]; break; case 5: v14 = light_blocked[rotation][4]; break; case 6: v14 = light_blocked[rotation][5]; break; case 7: v14 = light_blocked[rotation][6]; break; case 8: v14 = light_blocked[rotation][0] & light_blocked[nextRotation][0]; break; case 9: v14 = light_blocked[rotation][1] & light_blocked[rotation][8]; break; case 10: v14 = light_blocked[rotation][2] & light_blocked[rotation][9]; break; case 11: v14 = light_blocked[rotation][3] & light_blocked[rotation][10]; break; case 12: v14 = light_blocked[rotation][4] & light_blocked[rotation][11]; break; case 13: v14 = light_blocked[rotation][5] & light_blocked[rotation][12]; break; case 14: v14 = light_blocked[rotation][6] & light_blocked[rotation][13]; break; case 15: v14 = light_blocked[rotation][8] & light_blocked[nextRotation][1]; break; case 16: v14 = light_blocked[rotation][8] | (light_blocked[rotation][9] & light_blocked[rotation][15]); break; case 17: edx = light_blocked[rotation][9]; edx |= light_blocked[rotation][10]; ebx = light_blocked[rotation][8]; esi = light_blocked[rotation][16]; ebx &= edx; edx &= esi; edi = light_blocked[rotation][15]; ebx |= edx; edx = light_blocked[rotation][10]; eax = light_blocked[rotation][9]; edx |= edi; eax &= edx; v14 = ebx | eax; break; case 18: edx = light_blocked[rotation][0]; ebx = light_blocked[rotation][9]; esi = light_blocked[rotation][10]; edx |= ebx; edi = light_blocked[rotation][11]; edx |= esi; ebx = light_blocked[rotation][17]; edx |= edi; ebx &= edx; edx = esi; esi = light_blocked[rotation][16]; edi = light_blocked[rotation][9]; edx &= esi; edx |= edi; edx |= ebx; v14 = edx; break; case 19: edx = light_blocked[rotation][17]; edi = light_blocked[rotation][18]; ebx = light_blocked[rotation][11]; edx |= edi; esi = light_blocked[rotation][10]; ebx &= edx; edx = light_blocked[rotation][9]; edx |= esi; ebx |= edx; edx = light_blocked[rotation][12]; edx &= edi; ebx |= edx; v14 = ebx; break; case 20: edx = light_blocked[rotation][2]; esi = light_blocked[rotation][11]; edi = light_blocked[rotation][12]; ebx = light_blocked[rotation][8]; edx |= esi; esi = light_blocked[rotation][9]; edx |= edi; edi = light_blocked[rotation][10]; ebx &= edx; edx &= esi; esi = light_blocked[rotation][17]; ebx |= edx; edx = light_blocked[rotation][16]; ebx |= edi; edi = light_blocked[rotation][18]; edx |= esi; esi = light_blocked[rotation][19]; edx |= edi; eax = light_blocked[rotation][11]; edx |= esi; eax &= edx; ebx |= eax; v14 = ebx; break; case 21: v14 = (light_blocked[rotation][8] & light_blocked[nextRotation][1]) | (light_blocked[rotation][15] & light_blocked[nextRotation][2]); break; case 22: edx = light_blocked[nextRotation][1]; ebx = light_blocked[rotation][15]; esi = light_blocked[rotation][21]; edx |= ebx; ebx = light_blocked[rotation][8]; edx |= esi; ebx &= edx; edx = light_blocked[rotation][9]; edi = esi; edx |= esi; esi = light_blocked[rotation][15]; edx &= esi; ebx |= edx; edx = esi; esi = light_blocked[rotation][16]; edx |= edi; edx &= esi; ebx |= edx; v14 = ebx; break; case 23: edx = light_blocked[rotation][3]; ebx = light_blocked[rotation][16]; esi = light_blocked[rotation][15]; ebx |= edx; edx = light_blocked[rotation][9]; edx &= esi; edi = light_blocked[rotation][22]; ebx |= edx; edx = light_blocked[rotation][17]; edx &= edi; ebx |= edx; v14 = ebx; break; case 24: edx = light_blocked[rotation][0]; edi = light_blocked[rotation][9]; ebx = light_blocked[rotation][10]; edx |= edi; esi = light_blocked[rotation][17]; edx |= ebx; edi = light_blocked[rotation][18]; edx |= esi; ebx = light_blocked[rotation][16]; edx |= edi; esi = light_blocked[rotation][16]; ebx &= edx; edx = light_blocked[rotation][15]; edi = light_blocked[rotation][23]; edx |= esi; esi = light_blocked[rotation][9]; edx |= edi; edi = light_blocked[rotation][8]; edx &= esi; edx |= edi; esi = light_blocked[rotation][22]; ebx |= edx; edx = light_blocked[rotation][15]; edi = light_blocked[rotation][23]; edx |= esi; esi = light_blocked[rotation][17]; edx |= edi; edx &= esi; ebx |= edx; edx = light_blocked[rotation][18]; edx &= edi; ebx |= edx; v14 = ebx; break; case 25: edx = light_blocked[rotation][8]; edi = light_blocked[rotation][15]; ebx = light_blocked[rotation][16]; edx |= edi; esi = light_blocked[rotation][23]; edx |= ebx; edi = light_blocked[rotation][24]; edx |= esi; ebx = light_blocked[rotation][9]; edx |= edi; esi = light_blocked[rotation][1]; ebx &= edx; edx = light_blocked[rotation][8]; edx &= esi; edi = light_blocked[rotation][16]; ebx |= edx; edx = light_blocked[rotation][8]; esi = light_blocked[rotation][17]; edx |= edi; edi = light_blocked[rotation][24]; esi |= edx; esi |= edi; esi &= light_blocked[rotation][10]; edi = light_blocked[rotation][23]; ebx |= esi; esi = light_blocked[rotation][17]; edx |= edi; ebx |= esi; esi = light_blocked[rotation][24]; edi = light_blocked[rotation][18]; edx |= esi; edx &= edi; esi = light_blocked[rotation][19]; ebx |= edx; edx = light_blocked[rotation][0]; eax = light_blocked[rotation][24]; edx |= esi; eax &= edx; ebx |= eax; v14 = ebx; break; case 26: ebx = light_blocked[rotation][8]; esi = light_blocked[nextRotation][1]; edi = light_blocked[nextRotation][2]; esi &= ebx; ebx = light_blocked[rotation][15]; ebx &= edi; eax = light_blocked[rotation][21]; ebx |= esi; eax &= light_blocked[nextRotation][3]; ebx |= eax; v14 = ebx; break; case 27: edx = light_blocked[nextRotation][0]; edi = light_blocked[rotation][15]; esi = light_blocked[rotation][21]; edx |= edi; edi = light_blocked[rotation][26]; edx |= esi; esi = light_blocked[rotation][22]; edx |= edi; edi = light_blocked[nextRotation][1]; esi &= edx; edx = light_blocked[rotation][8]; ebx = light_blocked[rotation][15]; edx &= edi; edx |= ebx; edi = light_blocked[rotation][16]; esi |= edx; edx = light_blocked[rotation][8]; eax = light_blocked[rotation][21]; edx |= edi; eax &= edx; esi |= eax; v14 = esi; break; case 28: ebx = light_blocked[rotation][9]; edi = light_blocked[rotation][16]; esi = light_blocked[rotation][23]; edx = light_blocked[nextRotation][0]; ebx |= edi; edi = light_blocked[rotation][15]; ebx |= esi; esi = light_blocked[rotation][8]; ebx &= edi; edi = light_blocked[rotation][21]; ebx |= esi; esi = light_blocked[rotation][22]; edx |= edi; edi = light_blocked[rotation][27]; edx |= esi; esi = light_blocked[rotation][16]; edx |= edi; edx &= esi; edi = light_blocked[rotation][17]; ebx |= edx; edx = light_blocked[rotation][9]; esi = light_blocked[rotation][23]; edx |= edi; edi = light_blocked[rotation][22]; edx |= esi; edx &= edi; ebx |= edx; edx = esi; edx &= light_blocked[rotation][27]; ebx |= edx; v14 = ebx; break; case 29: edx = light_blocked[rotation][8]; edi = light_blocked[rotation][16]; ebx = light_blocked[rotation][23]; edx |= edi; esi = light_blocked[rotation][15]; ebx |= edx; edx = light_blocked[rotation][9]; edx &= esi; edi = light_blocked[rotation][22]; ebx |= edx; edx = light_blocked[rotation][17]; edx &= edi; esi = light_blocked[rotation][28]; ebx |= edx; edx = light_blocked[rotation][24]; edx &= esi; ebx |= edx; v14 = ebx; break; case 30: ebx = light_blocked[rotation][8]; esi = light_blocked[nextRotation][1]; edi = light_blocked[nextRotation][2]; esi &= ebx; ebx = light_blocked[rotation][15]; ebx &= edi; edi = light_blocked[nextRotation][3]; esi |= ebx; ebx = light_blocked[rotation][21]; ebx &= edi; eax = light_blocked[rotation][26]; ebx |= esi; eax &= light_blocked[nextRotation][4]; ebx |= eax; v14 = ebx; break; case 31: edx = light_blocked[rotation][8]; esi = light_blocked[nextRotation][1]; edi = light_blocked[rotation][15]; edx &= esi; ebx = light_blocked[rotation][21]; edx |= edi; esi = light_blocked[rotation][22]; ebx |= edx; edx = light_blocked[rotation][8]; edi = light_blocked[rotation][27]; edx |= esi; esi = light_blocked[rotation][26]; edx |= edi; edx &= esi; ebx |= edx; edx = edi; edx &= light_blocked[rotation][30]; ebx |= edx; v14 = ebx; break; case 32: ebx = light_blocked[rotation][8]; edi = light_blocked[rotation][9]; esi = light_blocked[rotation][16]; ebx |= edi; edi = light_blocked[rotation][23]; ebx |= esi; esi = light_blocked[rotation][28]; ebx |= edi; ebx |= esi; esi = light_blocked[rotation][15]; esi &= ebx; edx = light_blocked[rotation][8]; edx &= light_blocked[nextRotation][1]; ebx = light_blocked[rotation][16]; esi |= edx; edx = light_blocked[rotation][8]; edx |= ebx; ebx = light_blocked[rotation][28]; edi = light_blocked[rotation][21]; ebx |= edx; ebx &= edi; edi = light_blocked[rotation][23]; ebx |= esi; esi = light_blocked[rotation][22]; edx |= edi; ebx |= esi; esi = light_blocked[rotation][28]; edi = light_blocked[rotation][27]; edx |= esi; edx &= edi; esi = light_blocked[rotation][31]; ebx |= edx; edx = light_blocked[rotation][0]; edi = light_blocked[rotation][28]; edx |= esi; edx &= edi; ebx |= edx; v14 = ebx; break; case 33: esi = light_blocked[rotation][8]; edi = light_blocked[nextRotation][1]; ebx = light_blocked[rotation][15]; esi &= edi; ebx &= light_blocked[nextRotation][2]; edi = light_blocked[nextRotation][3]; esi |= ebx; ebx = light_blocked[rotation][21]; ebx &= edi; edi = light_blocked[nextRotation][4]; esi |= ebx; ebx = light_blocked[rotation][26]; ebx &= edi; eax = light_blocked[rotation][30]; ebx |= esi; eax &= light_blocked[nextRotation][5]; ebx |= eax; v14 = ebx; break; case 34: edx = light_blocked[nextRotation][2]; edi = light_blocked[rotation][26]; ebx = light_blocked[rotation][30]; edx |= edi; esi = light_blocked[rotation][15]; edx |= ebx; ebx = light_blocked[rotation][8]; edi = light_blocked[rotation][21]; ebx &= edx; edx &= esi; esi = light_blocked[rotation][22]; ebx |= edx; edx = light_blocked[rotation][16]; ebx |= edi; edi = light_blocked[rotation][27]; edx |= esi; esi = light_blocked[rotation][31]; edx |= edi; eax = light_blocked[rotation][26]; edx |= esi; eax &= edx; ebx |= eax; v14 = ebx; break; case 35: ebx = light_blocked[rotation][8]; esi = light_blocked[nextRotation][1]; edi = light_blocked[nextRotation][2]; esi &= ebx; ebx = light_blocked[rotation][15]; ebx &= edi; edi = light_blocked[nextRotation][3]; esi |= ebx; ebx = light_blocked[rotation][21]; ebx &= edi; edi = light_blocked[nextRotation][4]; esi |= ebx; ebx = light_blocked[rotation][26]; ebx &= edi; edi = light_blocked[nextRotation][5]; esi |= ebx; ebx = light_blocked[rotation][30]; ebx &= edi; eax = light_blocked[rotation][33]; ebx |= esi; eax &= light_blocked[nextRotation][6]; ebx |= eax; v14 = ebx; break; default: assert(false && "Should be unreachable"); } if (v14 == 0) { // TODO: Check. int tile = obj->tile + v70[rotation][index]; if (hexGridTileIsValid(tile)) { bool v12 = true; ObjectListNode* objectListNode = objectTable[tile]; while (objectListNode != NULL) { if ((objectListNode->obj->flags & OBJECT_HIDDEN) == 0) { if (objectListNode->obj->elevation > obj->elevation) { break; } if (objectListNode->obj->elevation == obj->elevation) { Rect v29; obj_bound(objectListNode->obj, &v29); rect_min_bound(&objectRect, &v29, &objectRect); v14 = (objectListNode->obj->flags & OBJECT_LIGHT_THRU) == 0; if (FID_TYPE(objectListNode->obj->fid) == OBJ_TYPE_WALL) { if ((objectListNode->obj->flags & OBJECT_FLAT) == 0) { Proto* proto; proto_ptr(objectListNode->obj->pid, &proto); if ((proto->wall.extendedFlags & 0x8000000) != 0 || (proto->wall.extendedFlags & 0x40000000) != 0) { if (rotation != ROTATION_W && rotation != ROTATION_NW && (rotation != ROTATION_NE || index >= 8) && (rotation != ROTATION_SW || index <= 15)) { v12 = false; } } else if ((proto->wall.extendedFlags & 0x10000000) != 0) { if (rotation != ROTATION_NE && rotation != ROTATION_NW) { v12 = false; } } else if ((proto->wall.extendedFlags & 0x20000000) != 0) { if (rotation != ROTATION_NE && rotation != ROTATION_E && rotation != ROTATION_W && rotation != ROTATION_NW && (rotation != ROTATION_SW || index <= 15)) { v12 = false; } } else { if (rotation != ROTATION_NE && rotation != ROTATION_E && (rotation != ROTATION_NW || index <= 7)) { v12 = false; } } } } else { if (v14 && rotation >= ROTATION_E && rotation <= ROTATION_SW) { v12 = false; } } if (v14) { break; } } } objectListNode = objectListNode->next; } if (v12) { adjustLightIntensity(obj->elevation, tile, v28[index]); } } } light_blocked[rotation][index] = v14; } } } if (rect != NULL) { Rect* lightDistanceRect = &(light_rect[obj->lightDistance]); memcpy(rect, lightDistanceRect, sizeof(*lightDistanceRect)); int x; int y; tile_coord(obj->tile, &x, &y, obj->elevation); x += 16; y += 8; x -= rect->lrx / 2; y -= rect->lry / 2; rectOffset(rect, x, y); rect_min_bound(rect, &objectRect, rect); } return 0; } // 0x48EABC static void obj_render_outline(Object* object, Rect* rect) { CacheEntry* cacheEntry; Art* art = art_ptr_lock(object->fid, &cacheEntry); if (art == NULL) { return; } int frameWidth = 0; int frameHeight = 0; art_frame_width_length(art, object->frame, object->rotation, &frameWidth, &frameHeight); Rect v49; v49.ulx = 0; v49.uly = 0; v49.lrx = frameWidth - 1; // FIXME: I'm not sure why it ignores frameHeight and makes separate call // to obtain height. int v8 = art_frame_length(art, object->frame, object->rotation); v49.lry = v8 - 1; Rect objectRect; if (object->tile == -1) { objectRect.ulx = object->sx; objectRect.uly = object->sy; objectRect.lrx = object->sx + frameWidth - 1; objectRect.lry = object->sy + frameHeight - 1; } else { int x; int y; tile_coord(object->tile, &x, &y, object->elevation); x += 16; y += 8; x += art->xOffsets[object->rotation]; y += art->yOffsets[object->rotation]; x += object->x; y += object->y; objectRect.ulx = x - frameWidth / 2; objectRect.uly = y - (frameHeight - 1); objectRect.lrx = objectRect.ulx + frameWidth - 1; objectRect.lry = y; object->sx = objectRect.ulx; object->sy = objectRect.uly; } Rect v32; rectCopy(&v32, rect); v32.ulx--; v32.uly--; v32.lrx++; v32.lry++; rect_inside_bound(&v32, &buf_rect, &v32); if (rect_inside_bound(&objectRect, &v32, &objectRect) == 0) { v49.ulx += objectRect.ulx - object->sx; v49.uly += objectRect.uly - object->sy; v49.lrx = v49.ulx + (objectRect.lrx - objectRect.ulx); v49.lry = v49.uly + (objectRect.lry - objectRect.uly); unsigned char* src = art_frame_data(art, object->frame, object->rotation); unsigned char* dest = back_buf + buf_full * object->sy + object->sx; int destStep = buf_full - frameWidth; unsigned char color; unsigned char* v47 = NULL; unsigned char* v48 = NULL; int v53 = object->outline & OUTLINE_PALETTED; int outlineType = object->outline & OUTLINE_TYPE_MASK; int v43; int v44; switch (outlineType) { case OUTLINE_TYPE_HOSTILE: color = 243; v53 = 0; v43 = 5; v44 = frameHeight / 5; break; case OUTLINE_TYPE_2: color = colorTable[31744]; v44 = 0; if (v53 != 0) { v47 = commonGrayTable; v48 = redBlendTable; } break; case OUTLINE_TYPE_4: color = colorTable[15855]; v44 = 0; if (v53 != 0) { v47 = commonGrayTable; v48 = wallBlendTable; } break; case OUTLINE_TYPE_FRIENDLY: v43 = 4; v44 = frameHeight / 4; color = 229; v53 = 0; break; case OUTLINE_TYPE_ITEM: v44 = 0; color = colorTable[30632]; if (v53 != 0) { v47 = commonGrayTable; v48 = redBlendTable; } break; case OUTLINE_TYPE_32: color = 61; v53 = 0; v43 = 1; v44 = frameHeight; break; default: color = colorTable[31775]; v53 = 0; v44 = 0; break; } unsigned char v54 = color; unsigned char* dest14 = dest; unsigned char* src15 = src; for (int y = 0; y < frameHeight; y++) { bool cycle = true; if (v44 != 0) { if (y % v44 == 0) { v54++; } if (v54 > v43 + color - 1) { v54 = color; } } int v22 = dest14 - back_buf; for (int x = 0; x < frameWidth; x++) { v22 = dest14 - back_buf; if (*src15 != 0 && cycle) { if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry && v22 > 0 && v22 % buf_full != 0) { unsigned char v20; if (v53 != 0) { v20 = v48[(v47[v54] << 8) + *(dest14 - 1)]; } else { v20 = v54; } *(dest14 - 1) = v20; } cycle = false; } else if (*src15 == 0 && !cycle) { if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) { int v21; if (v53 != 0) { v21 = v48[(v47[v54] << 8) + *dest14]; } else { v21 = v54; } *dest14 = v21 & 0xFF; } cycle = true; } dest14++; src15++; } if (*(src15 - 1) != 0) { if (v22 < buf_size) { int v23 = frameWidth - 1; if (v23 >= v49.ulx && v23 <= v49.lrx && y >= v49.uly && y <= v49.lry) { if (v53 != 0) { *dest14 = v48[(v47[v54] << 8) + *dest14]; } else { *dest14 = v54; } } } } dest14 += destStep; } for (int x = 0; x < frameWidth; x++) { bool cycle = true; unsigned char v28 = color; unsigned char* dest27 = dest + x; unsigned char* src27 = src + x; for (int y = 0; y < frameHeight; y++) { if (v44 != 0) { if (y % v44 == 0) { v28++; } if (v28 > color + v43 - 1) { v28 = color; } } if (*src27 != 0 && cycle) { if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) { unsigned char* v29 = dest27 - buf_full; if (v29 >= back_buf) { if (v53) { *v29 = v48[(v47[v28] << 8) + *v29]; } else { *v29 = v28; } } } cycle = false; } else if (*src27 == 0 && !cycle) { if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) { if (v53) { *dest27 = v48[(v47[v28] << 8) + *dest27]; } else { *dest27 = v28; } } cycle = true; } dest27 += buf_full; src27 += frameWidth; } if (src27[-frameWidth] != 0) { if (dest27 - back_buf < buf_size) { int y = frameHeight - 1; if (x >= v49.ulx && x <= v49.lrx && y >= v49.uly && y <= v49.lry) { if (v53) { *dest27 = v48[(v47[v28] << 8) + *dest27]; } else { *dest27 = v28; } } } } } } art_ptr_unlock(cacheEntry); } // 0x48F1B0 static void obj_render_object(Object* object, Rect* rect, int light) { int type = FID_TYPE(object->fid); if (art_get_disable(type)) { return; } CacheEntry* cacheEntry; Art* art = art_ptr_lock(object->fid, &cacheEntry); if (art == NULL) { return; } int frameWidth = art_frame_width(art, object->frame, object->rotation); int frameHeight = art_frame_length(art, object->frame, object->rotation); Rect objectRect; if (object->tile == -1) { objectRect.ulx = object->sx; objectRect.uly = object->sy; objectRect.lrx = object->sx + frameWidth - 1; objectRect.lry = object->sy + frameHeight - 1; } else { int objectScreenX; int objectScreenY; tile_coord(object->tile, &objectScreenX, &objectScreenY, object->elevation); objectScreenX += 16; objectScreenY += 8; objectScreenX += art->xOffsets[object->rotation]; objectScreenY += art->yOffsets[object->rotation]; objectScreenX += object->x; objectScreenY += object->y; objectRect.ulx = objectScreenX - frameWidth / 2; objectRect.uly = objectScreenY - (frameHeight - 1); objectRect.lrx = objectRect.ulx + frameWidth - 1; objectRect.lry = objectScreenY; object->sx = objectRect.ulx; object->sy = objectRect.uly; } if (rect_inside_bound(&objectRect, rect, &objectRect) != 0) { art_ptr_unlock(cacheEntry); return; } unsigned char* src = art_frame_data(art, object->frame, object->rotation); unsigned char* src2 = src; int v50 = objectRect.ulx - object->sx; int v49 = objectRect.uly - object->sy; src += frameWidth * v49 + v50; int objectWidth = objectRect.lrx - objectRect.ulx + 1; int objectHeight = objectRect.lry - objectRect.uly + 1; if (type == 6) { trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf + buf_full * objectRect.uly + objectRect.ulx, buf_full); art_ptr_unlock(cacheEntry); return; } if (type == 2 || type == 3) { if ((obj_dude->flags & OBJECT_HIDDEN) == 0 && (object->flags & OBJECT_FLAG_0xFC000) == 0) { Proto* proto; proto_ptr(object->pid, &proto); bool v17; int extendedFlags = proto->critter.extendedFlags; if ((extendedFlags & 0x8000000) != 0 || (extendedFlags & 0x80000000) != 0) { // TODO: Probably wrong. v17 = tile_in_front_of(object->tile, obj_dude->tile); if (!v17 || !tile_to_right_of(object->tile, obj_dude->tile) || (object->flags & OBJECT_WALL_TRANS_END) == 0) { // nothing } else { v17 = false; } } else if ((extendedFlags & 0x10000000) != 0) { // NOTE: Uses bitwise OR, so both functions are evaluated. v17 = tile_in_front_of(object->tile, obj_dude->tile) || tile_to_right_of(obj_dude->tile, object->tile); } else if ((extendedFlags & 0x20000000) != 0) { v17 = tile_in_front_of(object->tile, obj_dude->tile) && tile_to_right_of(obj_dude->tile, object->tile); } else { v17 = tile_to_right_of(obj_dude->tile, object->tile); if (v17 && tile_in_front_of(obj_dude->tile, object->tile) && (object->flags & OBJECT_WALL_TRANS_END) != 0) { v17 = 0; } } if (v17) { CacheEntry* eggHandle; Art* egg = art_ptr_lock(obj_egg->fid, &eggHandle); if (egg == NULL) { return; } int eggWidth; int eggHeight; art_frame_width_length(egg, 0, 0, &eggWidth, &eggHeight); int eggScreenX; int eggScreenY; tile_coord(obj_egg->tile, &eggScreenX, &eggScreenY, obj_egg->elevation); eggScreenX += 16; eggScreenY += 8; eggScreenX += egg->xOffsets[0]; eggScreenY += egg->yOffsets[0]; eggScreenX += obj_egg->x; eggScreenY += obj_egg->y; Rect eggRect; eggRect.ulx = eggScreenX - eggWidth / 2; eggRect.uly = eggScreenY - (eggHeight - 1); eggRect.lrx = eggRect.ulx + eggWidth - 1; eggRect.lry = eggScreenY; obj_egg->sx = eggRect.ulx; obj_egg->sy = eggRect.uly; Rect updatedEggRect; if (rect_inside_bound(&eggRect, &objectRect, &updatedEggRect) == 0) { Rect rects[4]; rects[0].ulx = objectRect.ulx; rects[0].uly = objectRect.uly; rects[0].lrx = objectRect.lrx; rects[0].lry = updatedEggRect.uly - 1; rects[1].ulx = objectRect.ulx; rects[1].uly = updatedEggRect.uly; rects[1].lrx = updatedEggRect.ulx - 1; rects[1].lry = updatedEggRect.lry; rects[2].ulx = updatedEggRect.lrx + 1; rects[2].uly = updatedEggRect.uly; rects[2].lrx = objectRect.lrx; rects[2].lry = updatedEggRect.lry; rects[3].ulx = objectRect.ulx; rects[3].uly = updatedEggRect.lry + 1; rects[3].lrx = objectRect.lrx; rects[3].lry = objectRect.lry; for (int i = 0; i < 4; i++) { Rect* v21 = &(rects[i]); if (v21->ulx <= v21->lrx && v21->uly <= v21->lry) { unsigned char* sp = src + frameWidth * (v21->uly - objectRect.uly) + (v21->ulx - objectRect.ulx); dark_trans_buf_to_buf(sp, v21->lrx - v21->ulx + 1, v21->lry - v21->uly + 1, frameWidth, back_buf, v21->ulx, v21->uly, buf_full, light); } } unsigned char* mask = art_frame_data(egg, 0, 0); intensity_mask_buf_to_buf( src + frameWidth * (updatedEggRect.uly - objectRect.uly) + (updatedEggRect.ulx - objectRect.ulx), updatedEggRect.lrx - updatedEggRect.ulx + 1, updatedEggRect.lry - updatedEggRect.uly + 1, frameWidth, back_buf + buf_full * updatedEggRect.uly + updatedEggRect.ulx, buf_full, mask + eggWidth * (updatedEggRect.uly - eggRect.uly) + (updatedEggRect.ulx - eggRect.ulx), eggWidth, light); art_ptr_unlock(eggHandle); art_ptr_unlock(cacheEntry); return; } art_ptr_unlock(eggHandle); } } } switch (object->flags & OBJECT_FLAG_0xFC000) { case OBJECT_TRANS_RED: dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, redBlendTable, commonGrayTable); break; case OBJECT_TRANS_WALL: dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, 0x10000, wallBlendTable, commonGrayTable); break; case OBJECT_TRANS_GLASS: dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, glassBlendTable, glassGrayTable); break; case OBJECT_TRANS_STEAM: dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, steamBlendTable, commonGrayTable); break; case OBJECT_TRANS_ENERGY: dark_translucent_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light, energyBlendTable, commonGrayTable); break; default: dark_trans_buf_to_buf(src, objectWidth, objectHeight, frameWidth, back_buf, objectRect.ulx, objectRect.uly, buf_full, light); break; } art_ptr_unlock(cacheEntry); } // Updates fid according to current violence level. // // 0x48FA14 void obj_fix_violence_settings(int* fid) { if (FID_TYPE(*fid) != OBJ_TYPE_CRITTER) { return; } bool shouldResetViolenceLevel = false; if (fix_violence_level == -1) { if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &fix_violence_level)) { fix_violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD; } shouldResetViolenceLevel = true; } int start; int end; switch (fix_violence_level) { case VIOLENCE_LEVEL_NONE: start = ANIM_BIG_HOLE_SF; end = ANIM_FALL_FRONT_BLOOD_SF; break; case VIOLENCE_LEVEL_MINIMAL: start = ANIM_BIG_HOLE_SF; end = ANIM_FIRE_DANCE_SF; break; case VIOLENCE_LEVEL_NORMAL: start = ANIM_BIG_HOLE_SF; end = ANIM_SLICED_IN_HALF_SF; break; default: // Do not replace anything. start = ANIM_COUNT + 1; end = ANIM_COUNT + 1; break; } int anim = FID_ANIM_TYPE(*fid); if (anim >= start && anim <= end) { anim = (anim == ANIM_FALL_BACK_BLOOD_SF) ? ANIM_FALL_BACK_SF : ANIM_FALL_FRONT_SF; *fid = art_id(OBJ_TYPE_CRITTER, *fid & 0xFFF, anim, (*fid & 0xF000) >> 12, (*fid & 0x70000000) >> 28); } if (shouldResetViolenceLevel) { fix_violence_level = -1; } } // 0x48FB08 static int obj_preload_sort(const void* a1, const void* a2) { // 0x51979C static int cd_order[9] = { 1, 0, 3, 5, 4, 2, 0, 0, 0, }; int v1 = *(int*)a1; int v2 = *(int*)a2; int v3 = cd_order[FID_TYPE(v1)]; int v4 = cd_order[FID_TYPE(v2)]; int cmp = v3 - v4; if (cmp != 0) { return cmp; } cmp = (v1 & 0xFFF) - (v2 & 0xFFF); if (cmp != 0) { return cmp; } cmp = ((v1 & 0xF000) >> 12) - (((v2 & 0xF000) >> 12)); if (cmp != 0) { return cmp; } cmp = ((v1 & 0xFF0000) >> 16) - (((v2 & 0xFF0000) >> 16)); return cmp; } ================================================ FILE: src/game/object.h ================================================ #ifndef FALLOUT_GAME_OBJECT_H_ #define FALLOUT_GAME_OBJECT_H_ #include "plib/db/db.h" #include "plib/gnw/rect.h" #include "game/inventry.h" #include "game/map_defs.h" #include "game/object_types.h" typedef struct ObjectWithFlags { int flags; Object* object; } ObjectWithFlags; extern unsigned char* wallBlendTable; extern unsigned char* glassBlendTable; extern unsigned char* steamBlendTable; extern unsigned char* energyBlendTable; extern unsigned char* redBlendTable; extern Object* moveBlockObj; extern unsigned char glassGrayTable[256]; extern unsigned char commonGrayTable[256]; extern Object* obj_egg; extern Object* obj_dude; int obj_init(unsigned char* buf, int width, int height, int pitch); void obj_reset(); void obj_exit(); int obj_load(File* stream); int obj_save(File* stream); void obj_render_pre_roof(Rect* rect, int elevation); void obj_render_post_roof(Rect* rect, int elevation); int obj_new(Object** objectPtr, int fid, int pid); int obj_pid_new(Object** objectPtr, int pid); int obj_copy(Object** a1, Object* a2); int obj_connect(Object* obj, int tile_index, int elev, Rect* rect); int obj_disconnect(Object* obj, Rect* rect); int obj_offset(Object* obj, int x, int y, Rect* rect); int obj_move(Object* a1, int a2, int a3, int elevation, Rect* a5); int obj_move_to_tile(Object* obj, int tile, int elevation, Rect* rect); int obj_reset_roof(); int obj_change_fid(Object* obj, int fid, Rect* rect); int obj_set_frame(Object* obj, int frame, Rect* rect); int obj_inc_frame(Object* obj, Rect* rect); int obj_dec_frame(Object* obj, Rect* rect); int obj_set_rotation(Object* obj, int direction, Rect* rect); int obj_inc_rotation(Object* obj, Rect* rect); int obj_dec_rotation(Object* obj, Rect* rect); void obj_rebuild_all_light(); int obj_set_light(Object* obj, int lightDistance, int lightIntensity, Rect* rect); int obj_get_visible_light(Object* obj); int obj_turn_on_light(Object* obj, Rect* rect); int obj_turn_off_light(Object* obj, Rect* rect); int obj_turn_on(Object* obj, Rect* rect); int obj_turn_off(Object* obj, Rect* rect); int obj_turn_on_outline(Object* obj, Rect* rect); int obj_turn_off_outline(Object* obj, Rect* rect); int obj_toggle_flat(Object* obj, Rect* rect); int obj_erase_object(Object* a1, Rect* a2); int obj_inven_free(Inventory* inventory); bool obj_action_can_use(Object* obj); bool obj_action_can_talk_to(Object* obj); bool obj_portal_is_walk_thru(Object* obj); Object* objFindObjPtrFromID(int a1); Object* obj_top_environment(Object* obj); void obj_remove_all(); Object* obj_find_first(); Object* obj_find_next(); Object* obj_find_first_at(int elevation); Object* obj_find_next_at(); Object* obj_find_first_at_tile(int elevation, int tile); Object* obj_find_next_at_tile(); void obj_bound(Object* obj, Rect* rect); bool obj_occupied(int tile_num, int elev); Object* obj_blocking_at(Object* a1, int tile_num, int elev); Object* obj_shoot_blocking_at(Object* obj, int tile, int elev); Object* obj_ai_blocking_at(Object* a1, int tile, int elevation); int obj_scroll_blocking_at(int tile_num, int elev); Object* obj_sight_blocking_at(Object* a1, int tile_num, int elev); int obj_dist(Object* object1, Object* object2); int obj_dist_with_tile(Object* object1, int tile1, Object* object2, int tile2); int obj_create_list(int tile, int elevation, int objectType, Object*** objectsPtr); void obj_delete_list(Object** objects); void translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, unsigned char* a9, unsigned char* a10); void dark_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light); void dark_translucent_trans_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destX, int destY, int destPitch, int light, unsigned char* a10, unsigned char* a11); void intensity_mask_buf_to_buf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destPitch, unsigned char* mask, int maskPitch, int light); int obj_outline_object(Object* obj, int a2, Rect* rect); int obj_remove_outline(Object* obj, Rect* rect); int obj_intersects_with(Object* object, int x, int y); int obj_create_intersect_list(int x, int y, int elevation, int objectType, ObjectWithFlags** entriesPtr); void obj_delete_intersect_list(ObjectWithFlags** a1); void obj_set_seen(int tile); void obj_clear_seen(); void obj_process_seen(); char* object_name(Object* obj); char* object_description(Object* obj); void obj_preload_art_cache(int flags); int obj_save_obj(File* stream, Object* object); int obj_load_obj(File* stream, Object** objectPtr, int elevation, Object* owner); int obj_save_dude(File* stream); int obj_load_dude(File* stream); void obj_fix_violence_settings(int* fid); #endif /* FALLOUT_GAME_OBJECT_H_ */ ================================================ FILE: src/game/object_types.h ================================================ #ifndef FALLOUT_GAME_OBJECT_TYPES_H_ #define FALLOUT_GAME_OBJECT_TYPES_H_ // Rotation typedef enum Rotation { ROTATION_NE, // 0 ROTATION_E, // 1 ROTATION_SE, // 2 ROTATION_SW, // 3 ROTATION_W, // 4 ROTATION_NW, // 5 ROTATION_COUNT, } Rotation; enum { OBJ_TYPE_ITEM, OBJ_TYPE_CRITTER, OBJ_TYPE_SCENERY, OBJ_TYPE_WALL, OBJ_TYPE_TILE, OBJ_TYPE_MISC, OBJ_TYPE_INTERFACE, OBJ_TYPE_INVENTORY, OBJ_TYPE_HEAD, OBJ_TYPE_BACKGROUND, OBJ_TYPE_SKILLDEX, OBJ_TYPE_COUNT, }; #define FID_TYPE(value) ((value) & 0xF000000) >> 24 #define PID_TYPE(value) (value) >> 24 #define SID_TYPE(value) (value) >> 24 typedef enum OutlineType { OUTLINE_TYPE_HOSTILE = 1, OUTLINE_TYPE_2 = 2, OUTLINE_TYPE_4 = 4, OUTLINE_TYPE_FRIENDLY = 8, OUTLINE_TYPE_ITEM = 16, OUTLINE_TYPE_32 = 32, } OutlineType; typedef enum ObjectFlags { OBJECT_HIDDEN = 0x01, OBJECT_TEMPORARY = 0x04, OBJECT_FLAT = 0x08, OBJECT_NO_BLOCK = 0x10, OBJECT_LIGHTING = 0x20, OBJECT_FLAG_0x400 = 0x400, OBJECT_MULTIHEX = 0x800, OBJECT_NO_HIGHLIGHT = 0x1000, OBJECT_USED = 0x2000, OBJECT_TRANS_RED = 0x4000, OBJECT_TRANS_NONE = 0x8000, OBJECT_TRANS_WALL = 0x10000, OBJECT_TRANS_GLASS = 0x20000, OBJECT_TRANS_STEAM = 0x40000, OBJECT_TRANS_ENERGY = 0x80000, OBJECT_IN_LEFT_HAND = 0x1000000, OBJECT_IN_RIGHT_HAND = 0x2000000, OBJECT_WORN = 0x4000000, OBJECT_WALL_TRANS_END = 0x10000000, OBJECT_LIGHT_THRU = 0x20000000, OBJECT_SEEN = 0x40000000, OBJECT_SHOOT_THRU = 0x80000000, OBJECT_IN_ANY_HAND = OBJECT_IN_LEFT_HAND | OBJECT_IN_RIGHT_HAND, OBJECT_EQUIPPED = OBJECT_IN_ANY_HAND | OBJECT_WORN, OBJECT_FLAG_0xFC000 = OBJECT_TRANS_ENERGY | OBJECT_TRANS_STEAM | OBJECT_TRANS_GLASS | OBJECT_TRANS_WALL | OBJECT_TRANS_NONE | OBJECT_TRANS_RED, OBJECT_OPEN_DOOR = OBJECT_SHOOT_THRU | OBJECT_LIGHT_THRU | OBJECT_NO_BLOCK, } ObjectFlags; typedef enum CritterFlags { CRITTER_BARTER = 0x02, CRITTER_NO_STEAL = 0x20, CRITTER_NO_DROP = 0x40, CRITTER_NO_LIMBS = 0x80, CRITTER_NO_AGE = 0x100, CRITTER_NO_HEAL = 0x200, CRITTER_INVULNERABLE = 0x400, CRITTER_FLAT = 0x800, CRITTER_SPECIAL_DEATH = 0x1000, CRITTER_LONG_LIMBS = 0x2000, CRITTER_NO_KNOCKBACK = 0x4000, } CritterFlags; #define OUTLINE_TYPE_MASK 0xFFFFFF #define OUTLINE_PALETTED 0x40000000 #define OUTLINE_DISABLED 0x80000000 // These two values are the same but stored in different fields. #define CONTAINER_FLAG_JAMMED 0x04000000 #define DOOR_FLAG_JAMMGED 0x04000000 #define CONTAINER_FLAG_LOCKED 0x02000000 #define DOOR_FLAG_LOCKED 0x02000000 typedef enum CritterManeuver { CRITTER_MANEUVER_NONE = 0, CRITTER_MANEUVER_0x01 = 0x01, CRITTER_MANEUVER_STOP_ATTACKING = 0x02, CRITTER_MANUEVER_FLEEING = 0x04, } CritterManeuver; typedef enum Dam { DAM_KNOCKED_OUT = 0x01, DAM_KNOCKED_DOWN = 0x02, DAM_CRIP_LEG_LEFT = 0x04, DAM_CRIP_LEG_RIGHT = 0x08, DAM_CRIP_ARM_LEFT = 0x10, DAM_CRIP_ARM_RIGHT = 0x20, DAM_BLIND = 0x40, DAM_DEAD = 0x80, DAM_HIT = 0x100, DAM_CRITICAL = 0x200, DAM_ON_FIRE = 0x400, DAM_BYPASS = 0x800, DAM_EXPLODE = 0x1000, DAM_DESTROY = 0x2000, DAM_DROP = 0x4000, DAM_LOSE_TURN = 0x8000, DAM_HIT_SELF = 0x10000, DAM_LOSE_AMMO = 0x20000, DAM_DUD = 0x40000, DAM_HURT_SELF = 0x80000, DAM_RANDOM_HIT = 0x100000, DAM_CRIP_RANDOM = 0x200000, DAM_BACKWASH = 0x400000, DAM_PERFORM_REVERSE = 0x800000, DAM_CRIP_LEG_ANY = DAM_CRIP_LEG_LEFT | DAM_CRIP_LEG_RIGHT, DAM_CRIP_ARM_ANY = DAM_CRIP_ARM_LEFT | DAM_CRIP_ARM_RIGHT, DAM_CRIP = DAM_CRIP_LEG_ANY | DAM_CRIP_ARM_ANY | DAM_BLIND, } Dam; #define OBJ_LOCKED 0x02000000 #define OBJ_JAMMED 0x04000000 typedef struct Object Object; typedef struct InventoryItem { Object* item; int quantity; } InventoryItem; // Represents inventory of the object. typedef struct Inventory { int length; int capacity; InventoryItem* items; } Inventory; typedef struct WeaponObjectData { int ammoQuantity; // obj_pudg.pudweapon.cur_ammo_quantity int ammoTypePid; // obj_pudg.pudweapon.cur_ammo_type_pid } WeaponObjectData; typedef struct AmmoItemData { int quantity; // obj_pudg.pudammo.cur_ammo_quantity } AmmoItemData; typedef struct MiscItemData { int charges; // obj_pudg.pudmisc_item.curr_charges } MiscItemData; typedef struct KeyItemData { int keyCode; // obj_pudg.pudkey_item.cur_key_code } KeyItemData; typedef union ItemObjectData { WeaponObjectData weapon; AmmoItemData ammo; MiscItemData misc; KeyItemData key; } ItemObjectData; typedef struct CritterCombatData { int maneuver; // obj_pud.combat_data.maneuver int ap; // obj_pud.combat_data.curr_mp int results; // obj_pud.combat_data.results int damageLastTurn; // obj_pud.combat_data.damage_last_turn int aiPacket; // obj_pud.combat_data.ai_packet int team; // obj_pud.combat_data.team_num union { Object* whoHitMe; // obj_pud.combat_data.who_hit_me int whoHitMeCid; }; } CritterCombatData; typedef struct CritterObjectData { int field_0; // obj_pud.reaction_to_pc CritterCombatData combat; // obj_pud.combat_data int hp; // obj_pud.curr_hp int radiation; // obj_pud.curr_rad int poison; // obj_pud.curr_poison } CritterObjectData; typedef struct DoorSceneryData { int openFlags; // obj_pudg.pudportal.cur_open_flags } DoorSceneryData; typedef struct StairsSceneryData { int destinationMap; // obj_pudg.pudstairs.destMap int destinationBuiltTile; // obj_pudg.pudstairs.destBuiltTile } StairsSceneryData; typedef struct ElevatorSceneryData { int type; int level; } ElevatorSceneryData; typedef struct LadderSceneryData { int destinationMap; int destinationBuiltTile; } LadderSceneryData; typedef union SceneryObjectData { DoorSceneryData door; StairsSceneryData stairs; ElevatorSceneryData elevator; LadderSceneryData ladder; } SceneryObjectData; typedef struct MiscObjectData { int map; int tile; int elevation; int rotation; } MiscObjectData; typedef struct ObjectData { Inventory inventory; union { CritterObjectData critter; struct { int flags; union { ItemObjectData item; SceneryObjectData scenery; MiscObjectData misc; }; }; }; } ObjectData; typedef struct Object { int id; // obj_id int tile; // obj_tile_num int x; // obj_x int y; // obj_y int sx; // obj_sx int sy; // obj_sy int frame; // obj_cur_frm int rotation; // obj_cur_rot int fid; // obj_fid int flags; // obj_flags int elevation; // obj_elev union { int field_2C_array[14]; ObjectData data; }; int pid; // obj_pid int cid; // obj_cid int lightDistance; // obj_light_distance int lightIntensity; // obj_light_intensity int outline; // obj_outline int sid; // obj_sid Object* owner; int field_80; } Object; #ifdef _WIN32 static_assert(sizeof(Object) == 132, "wrong size"); #endif typedef struct ObjectListNode { Object* obj; struct ObjectListNode* next; } ObjectListNode; #define BUILT_TILE_TILE_MASK 0x3FFFFFF #define BUILT_TILE_ELEVATION_MASK 0xE0000000 #define BUILT_TILE_ELEVATION_SHIFT 29 #define BUILT_TILE_ROTATION_MASK 0x1C000000 #define BUILT_TILE_ROTATION_SHIFT 26 static inline int builtTileGetTile(int builtTile) { return builtTile & BUILT_TILE_TILE_MASK; } static inline int builtTileGetElevation(int builtTile) { return (builtTile & BUILT_TILE_ELEVATION_MASK) >> BUILT_TILE_ELEVATION_SHIFT; } static inline int builtTileGetRotation(int builtTile) { return (builtTile & BUILT_TILE_ROTATION_MASK) >> BUILT_TILE_ROTATION_SHIFT; } static inline int builtTileCreate(int tile, int elevation) { return tile | ((elevation << BUILT_TILE_ELEVATION_SHIFT) & BUILT_TILE_ELEVATION_MASK); } #endif /* FALLOUT_GAME_OBJECT_TYPES_H_ */ ================================================ FILE: src/game/options.c ================================================ #include "game/options.h" #include #include #include #include "plib/color/color.h" #include "game/art.h" #include "game/combat.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/graphlib.h" #include "game/gsound.h" #include "game/loadsave.h" #include "game/message.h" #include "plib/gnw/memory.h" #include "game/scripts.h" #include "plib/gnw/text.h" #include "game/textobj.h" #include "game/tile.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #define PREFERENCES_WINDOW_WIDTH 640 #define PREFERENCES_WINDOW_HEIGHT 480 #define OPTIONS_WINDOW_BUTTONS_COUNT 10 #define PRIMARY_OPTION_VALUE_COUNT 4 #define SECONDARY_OPTION_VALUE_COUNT 2 #define GAMMA_MIN 1.0 #define GAMMA_MAX 1.17999267578125 #define GAMMA_STEP 0.01124954223632812 typedef enum Preference { PREF_GAME_DIFFICULTY, PREF_COMBAT_DIFFICULTY, PREF_VIOLENCE_LEVEL, PREF_TARGET_HIGHLIGHT, PREF_COMBAT_LOOKS, PREF_COMBAT_MESSAGES, PREF_COMBAT_TAUNTS, PREF_LANGUAGE_FILTER, PREF_RUNNING, PREF_SUBTITLES, PREF_ITEM_HIGHLIGHT, PREF_COMBAT_SPEED, PREF_TEXT_BASE_DELAY, PREF_MASTER_VOLUME, PREF_MUSIC_VOLUME, PREF_SFX_VOLUME, PREF_SPEECH_VOLUME, PREF_BRIGHTNESS, PREF_MOUSE_SENSITIVIY, PREF_COUNT, FIRST_PRIMARY_PREF = PREF_GAME_DIFFICULTY, LAST_PRIMARY_PREF = PREF_COMBAT_LOOKS, PRIMARY_PREF_COUNT = LAST_PRIMARY_PREF - FIRST_PRIMARY_PREF + 1, FIRST_SECONDARY_PREF = PREF_COMBAT_MESSAGES, LAST_SECONDARY_PREF = PREF_ITEM_HIGHLIGHT, SECONDARY_PREF_COUNT = LAST_SECONDARY_PREF - FIRST_SECONDARY_PREF + 1, FIRST_RANGE_PREF = PREF_COMBAT_SPEED, LAST_RANGE_PREF = PREF_MOUSE_SENSITIVIY, RANGE_PREF_COUNT = LAST_RANGE_PREF - FIRST_RANGE_PREF + 1, } Preference; typedef enum PauseWindowFrm { PAUSE_WINDOW_FRM_BACKGROUND, PAUSE_WINDOW_FRM_DONE_BOX, PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP, PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN, PAUSE_WINDOW_FRM_COUNT, } PauseWindowFrm; typedef enum OptionsWindowFrm { OPTIONS_WINDOW_FRM_BACKGROUND, OPTIONS_WINDOW_FRM_BUTTON_ON, OPTIONS_WINDOW_FRM_BUTTON_OFF, OPTIONS_WINDOW_FRM_COUNT, } OptionsWindowFrm; typedef enum PreferencesWindowFrm { PREFERENCES_WINDOW_FRM_BACKGROUND, // Knob (for range preferences) PREFERENCES_WINDOW_FRM_KNOB_OFF, // 4-way switch (for primary preferences) PREFERENCES_WINDOW_FRM_PRIMARY_SWITCH, // 2-way switch (for secondary preferences) PREFERENCES_WINDOW_FRM_SECONDARY_SWITCH, PREFERENCES_WINDOW_FRM_CHECKBOX_ON, PREFERENCES_WINDOW_FRM_CHECKBOX_OFF, PREFERENCES_WINDOW_FRM_6, // Active knob (for range preferences) PREFERENCES_WINDOW_FRM_KNOB_ON, PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP, PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN, PREFERENCES_WINDOW_FRM_COUNT, } PreferencesWindowFrm; #pragma pack(2) typedef struct PreferenceDescription { // The number of options. short valuesCount; // Direction of rotation: // 0 - clockwise (incrementing value), // 1 - counter-clockwise (decrementing value) short direction; short knobX; short knobY; // Min x coordinate of the preference control bounding box. short minX; // Max x coordinate of the preference control bounding box. short maxX; short labelIds[PRIMARY_OPTION_VALUE_COUNT]; int btn; char name[32]; double minValue; double maxValue; int* valuePtr; } PreferenceDescription; #pragma pack() static_assert(sizeof(PreferenceDescription) == 76, "wrong size"); static int OptnStart(); static int OptnEnd(); static void ShadeScreen(bool a1); static int do_prefscreen(); static int PrefStart(); static void DoThing(int eventCode); static void UpdateThing(int index); static int PrefEnd(); static void SetSystemPrefs(); static int SavePrefs(bool save); static void SetDefaults(bool a1); static void SaveSettings(); static void RestoreSettings(); static void JustUpdate(); // 0x48FBD0 static const short row1Ytab[PRIMARY_PREF_COUNT] = { 48, 125, 203, 286, 363, }; // 0x48FBDA static const short row2Ytab[SECONDARY_PREF_COUNT] = { 49, 116, 181, 247, 313, 380, }; // 0x48FBE6 static const short row3Ytab[RANGE_PREF_COUNT] = { 19, 94, 165, 216, 268, 319, 369, 420, }; // x offsets for primary preferences from the knob position // 0x48FBF6 static const short bglbx[PRIMARY_OPTION_VALUE_COUNT] = { 2, 25, 46, 46, }; // y offsets for primary preference option values from the knob position // 0x48FBFE static const short bglby[PRIMARY_OPTION_VALUE_COUNT] = { 10, -4, 10, 31, }; // x offsets for secondary prefrence option values from the knob position // 0x48FC06 static const short smlbx[SECONDARY_OPTION_VALUE_COUNT] = { 4, 21, }; // 0x5197C0 static int opgrphs[OPTIONS_WINDOW_FRM_COUNT] = { 220, // opbase.frm - character editor 222, // opbtnon.frm - character editor 221, // opbtnoff.frm - character editor }; // 0x5197CC static int prfgrphs[PREFERENCES_WINDOW_FRM_COUNT] = { 240, // prefscrn.frm - options screen 241, // prfsldof.frm - options screen 242, // prfbknbs.frm - options screen 243, // prflknbs.frm - options screen 244, // prfxin.frm - options screen 245, // prfxout.frm - options screen 246, // prefcvr.frm - options screen 247, // prfsldon.frm - options screen 8, // lilredup.frm - little red button up 9, // lilreddn.frm - little red button down }; // 0x6637D0 static Size ginfo[OPTIONS_WINDOW_FRM_COUNT]; // 0x6637E8 static MessageList optn_msgfl; // 0x6637F0 static Size ginfo2[PREFERENCES_WINDOW_FRM_COUNT]; // 0x663840 static MessageListItem optnmesg; // 0x663850 static unsigned char* prfbmp[PREFERENCES_WINDOW_FRM_COUNT]; // 0x663878 static unsigned char* opbtns[OPTIONS_WINDOW_BUTTONS_COUNT]; // 0x6638A0 static CacheEntry* grphkey2[PREFERENCES_WINDOW_FRM_COUNT]; // 0x6638C8 static double text_delay_back; // 0x6638D0 static double gamma_value; // 0x6638D8 static double gamma_value_back; // 0x6638E0 static double text_delay; // 0x6638E8 static double mouse_sens; // 0x6638F0 static double mouse_sens_back; // 0x6638F8 static unsigned char* prefbuf; // 0x6638FC static bool mouse_3d_was_on; // 0x663900 static int optnwin; // 0x663904 static int prfwin; // 0x663908 static unsigned char* winbuf; // 0x66390C static CacheEntry* grphkey[OPTIONS_WINDOW_FRM_COUNT]; // 0x663918 static unsigned char* opbmp[OPTIONS_WINDOW_FRM_COUNT]; // This array stores backup for only integer-representable prefs. Because // `player_speedup` is not a part of prefs enum, it's value is stored in // `PREF_TEXT_BASE_DELAY`. // // 0x663924 static int settings_backup[PREF_COUNT]; // 0x663970 static int sndfx_volume; // 0x663974 static int subtitles; // 0x663978 static int language_filter; // 0x66397C static int speech_volume; // 0x663980 static int master_volume; // 0x663984 static int player_speedup; // 0x663988 static int combat_taunts; // 0x66398C static int fontsave; // 0x663990 static int music_volume; // 0x663994 static bool bk_enable; // 0x663998 static int prf_running; // 0x66399C static int combat_speed; // 0x6639A0 static int plyrspdbid; // 0x6639A4 static int item_highlight; // 0x6639A8 static bool changed; // 0x6639AC static int combat_messages; // 0x6639B0 static int target_highlight; // 0x6639B4 static int combat_difficulty; // 0x6639B8 static int violence_level; // 0x6639BC static int game_difficulty; // 0x6639C0 static int combatLookValue; // 0x5197F8 static PreferenceDescription btndat[PREF_COUNT] = { { 3, 0, 76, 71, 0, 0, { 203, 204, 205, 0 }, 0, GAME_CONFIG_GAME_DIFFICULTY_KEY, 0, 0, &game_difficulty }, { 3, 0, 76, 149, 0, 0, { 206, 204, 208, 0 }, 0, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, 0, 0, &combat_difficulty }, { 4, 0, 76, 226, 0, 0, { 214, 215, 204, 216 }, 0, GAME_CONFIG_VIOLENCE_LEVEL_KEY, 0, 0, &violence_level }, { 3, 0, 76, 309, 0, 0, { 202, 201, 213, 0 }, 0, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, 0, 0, &target_highlight }, { 2, 0, 76, 387, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_COMBAT_LOOKS_KEY, 0, 0, &combatLookValue }, { 2, 0, 299, 74, 0, 0, { 211, 212, 0, 0 }, 0, GAME_CONFIG_COMBAT_MESSAGES_KEY, 0, 0, &combat_messages }, { 2, 0, 299, 141, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_COMBAT_TAUNTS_KEY, 0, 0, &combat_taunts }, { 2, 0, 299, 207, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_LANGUAGE_FILTER_KEY, 0, 0, &language_filter }, { 2, 0, 299, 271, 0, 0, { 209, 219, 0, 0 }, 0, GAME_CONFIG_RUNNING_KEY, 0, 0, &prf_running }, { 2, 0, 299, 338, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_SUBTITLES_KEY, 0, 0, &subtitles }, { 2, 0, 299, 404, 0, 0, { 202, 201, 0, 0 }, 0, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, 0, 0, &item_highlight }, { 2, 0, 374, 50, 0, 0, { 207, 210, 0, 0 }, 0, GAME_CONFIG_COMBAT_SPEED_KEY, 0.0, 50.0, &combat_speed }, { 3, 0, 374, 125, 0, 0, { 217, 209, 218, 0 }, 0, GAME_CONFIG_TEXT_BASE_DELAY_KEY, 1.0, 6.0, NULL }, { 4, 0, 374, 196, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_MASTER_VOLUME_KEY, 0, 32767.0, &master_volume }, { 4, 0, 374, 247, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_MUSIC_VOLUME_KEY, 0, 32767.0, &music_volume }, { 4, 0, 374, 298, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_SNDFX_VOLUME_KEY, 0, 32767.0, &sndfx_volume }, { 4, 0, 374, 349, 0, 0, { 202, 221, 209, 222 }, 0, GAME_CONFIG_SPEECH_VOLUME_KEY, 0, 32767.0, &speech_volume }, { 2, 0, 374, 400, 0, 0, { 207, 223, 0, 0 }, 0, GAME_CONFIG_BRIGHTNESS_KEY, 1.0, 1.17999267578125, NULL }, { 2, 0, 374, 451, 0, 0, { 207, 218, 0, 0 }, 0, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, 1.0, 2.5, NULL }, }; // 0x48FC48 int do_options() { return do_optionsFunc(-1); } // 0x48FC50 int do_optionsFunc(int initialKeyCode) { if (OptnStart() == -1) { debug_printf("\nOPTION MENU: Error loading option dialog data!\n"); return -1; } int rc = -1; while (rc == -1) { int keyCode = get_input(); bool showPreferences = false; if (initialKeyCode != -1) { keyCode = initialKeyCode; rc = 1; } if (keyCode == KEY_ESCAPE || keyCode == 504 || game_user_wants_to_quit != 0) { rc = 0; } else { switch (keyCode) { case KEY_RETURN: case KEY_UPPERCASE_O: case KEY_LOWERCASE_O: case KEY_UPPERCASE_D: case KEY_LOWERCASE_D: gsound_play_sfx_file("ib1p1xx1"); rc = 0; break; case KEY_UPPERCASE_S: case KEY_LOWERCASE_S: case 500: if (SaveGame(LOAD_SAVE_MODE_NORMAL) == 1) { rc = 1; } break; case KEY_UPPERCASE_L: case KEY_LOWERCASE_L: case 501: if (LoadGame(LOAD_SAVE_MODE_NORMAL) == 1) { rc = 1; } break; case KEY_UPPERCASE_P: case KEY_LOWERCASE_P: gsound_play_sfx_file("ib1p1xx1"); // FALLTHROUGH case 502: // PREFERENCES showPreferences = true; break; case KEY_PLUS: case KEY_EQUAL: IncGamma(); break; case KEY_UNDERSCORE: case KEY_MINUS: DecGamma(); break; } } if (showPreferences) { do_prefscreen(); } else { switch (keyCode) { case KEY_F12: dump_screen(); break; case KEY_UPPERCASE_E: case KEY_LOWERCASE_E: case KEY_CTRL_Q: case KEY_CTRL_X: case KEY_F10: case 503: game_quit_with_confirm(); break; } } } OptnEnd(); return rc; } // 0x48FE14 static int OptnStart() { fontsave = text_curr(); if (!message_init(&optn_msgfl)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "options.msg"); if (!message_load(&optn_msgfl, path)) { return -1; } for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, opgrphs[index], 0, 0, 0); opbmp[index] = art_lock(fid, &(grphkey[index]), &(ginfo[index].width), &(ginfo[index].height)); if (opbmp[index] == NULL) { while (--index >= 0) { art_ptr_unlock(grphkey[index]); } message_exit(&optn_msgfl); return -1; } } int cycle = 0; for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) { opbtns[index] = (unsigned char*)mem_malloc(ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height + 1024); if (opbtns[index] == NULL) { while (--index >= 0) { mem_free(opbtns[index]); } for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } message_exit(&optn_msgfl); return -1; } cycle = cycle ^ 1; memcpy(opbtns[index], opbmp[cycle + 1], ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height); } int optionsWindowX = (640 - ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].width) / 2; int optionsWindowY = (480 - ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].height) / 2 - 60; optnwin = win_add(optionsWindowX, optionsWindowY, ginfo[0].width, ginfo[0].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (optnwin == -1) { for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) { mem_free(opbtns[index]); } for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } message_exit(&optn_msgfl); return -1; } bk_enable = map_disable_bk_processes(); mouse_3d_was_on = gmouse_3d_is_on(); if (mouse_3d_was_on) { gmouse_3d_off(); } gmouse_set_cursor(MOUSE_CURSOR_ARROW); winbuf = win_get_buf(optnwin); memcpy(winbuf, opbmp[OPTIONS_WINDOW_FRM_BACKGROUND], ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].width * ginfo[OPTIONS_WINDOW_FRM_BACKGROUND].height); text_font(103); int textY = (ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height - text_height()) / 2 + 1; int buttonY = 17; for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index += 2) { char text[128]; const char* msg = getmsg(&optn_msgfl, &optnmesg, index / 2); strcpy(text, msg); int textX = (ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width - text_width(text)) / 2; if (textX < 0) { textX = 0; } text_to_buf(opbtns[index] + ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * textY + textX, text, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, colorTable[18979]); text_to_buf(opbtns[index + 1] + ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width * textY + textX, text, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, colorTable[14723]); int btn = win_register_button(optnwin, 13, buttonY, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].width, ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height, -1, -1, -1, index / 2 + 500, opbtns[index], opbtns[index + 1], NULL, 32); if (btn != -1) { win_register_button_sound_func(btn, gsound_lrg_butt_press, gsound_lrg_butt_release); } buttonY += ginfo[OPTIONS_WINDOW_FRM_BUTTON_ON].height + 3; } text_font(101); win_draw(optnwin); return 0; } // 0x490244 static int OptnEnd() { win_delete(optnwin); text_font(fontsave); message_exit(&optn_msgfl); for (int index = 0; index < OPTIONS_WINDOW_BUTTONS_COUNT; index++) { mem_free(opbtns[index]); } for (int index = 0; index < OPTIONS_WINDOW_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } if (mouse_3d_was_on) { gmouse_3d_on(); } if (bk_enable) { map_enable_bk_processes(); } return 0; } // 0x4902B0 int PauseWindow(bool a1) { // 0x48FC0C static const int graphicIds[PAUSE_WINDOW_FRM_COUNT] = { 208, // charwin.frm - character editor 209, // donebox.frm - character editor 8, // lilredup.frm - little red button up 9, // lilreddn.frm - little red button down }; unsigned char* frmData[PAUSE_WINDOW_FRM_COUNT]; CacheEntry* frmHandles[PAUSE_WINDOW_FRM_COUNT]; Size frmSizes[PAUSE_WINDOW_FRM_COUNT]; bool gameMouseWasVisible; if (!a1) { bk_enable = map_disable_bk_processes(); cycle_disable(); gameMouseWasVisible = gmouse_3d_is_on(); if (gameMouseWasVisible) { gmouse_3d_off(); } } gmouse_set_cursor(MOUSE_CURSOR_ARROW); ShadeScreen(a1); for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, graphicIds[index], 0, 0, 0); frmData[index] = art_lock(fid, &(frmHandles[index]), &(frmSizes[index].width), &(frmSizes[index].height)); if (frmData[index] == NULL) { while (--index >= 0) { art_ptr_unlock(frmHandles[index]); } debug_printf("\n** Error loading pause window graphics! **\n"); return -1; } } if (!message_init(&optn_msgfl)) { // FIXME: Leaking graphics. return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "options.msg"); if (!message_load(&optn_msgfl, path)) { // FIXME: Leaking graphics. return -1; } int pauseWindowX = (640 - frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width) / 2; int pauseWindowY = (480 - frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height) / 2; if (a1) { pauseWindowX -= 65; pauseWindowY -= 24; } else { pauseWindowY -= 54; } int window = win_add(pauseWindowX, pauseWindowY, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (window == -1) { for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } message_exit(&optn_msgfl); debug_printf("\n** Error opening pause window! **\n"); return -1; } unsigned char* windowBuffer = win_get_buf(window); memcpy(windowBuffer, frmData[PAUSE_WINDOW_FRM_BACKGROUND], frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].height); trans_buf_to_buf(frmData[PAUSE_WINDOW_FRM_DONE_BOX], frmSizes[PAUSE_WINDOW_FRM_DONE_BOX].width, frmSizes[PAUSE_WINDOW_FRM_DONE_BOX].height, frmSizes[PAUSE_WINDOW_FRM_DONE_BOX].width, windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 42 + 13, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width); fontsave = text_curr(); text_font(103); char* messageItemText; messageItemText = getmsg(&optn_msgfl, &optnmesg, 300); text_to_buf(windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 45 + 52, messageItemText, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, colorTable[18979]); text_font(104); messageItemText = getmsg(&optn_msgfl, &optnmesg, 301); strcpy(path, messageItemText); int length = text_width(path); text_to_buf(windowBuffer + frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width * 10 + 2 + (frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width - length) / 2, path, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, frmSizes[PAUSE_WINDOW_FRM_BACKGROUND].width, colorTable[18979]); int doneBtn = win_register_button(window, 26, 46, frmSizes[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, frmSizes[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 504, frmData[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_UP], frmData[PAUSE_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (doneBtn != -1) { win_register_button_sound_func(doneBtn, gsound_red_butt_press, gsound_red_butt_release); } win_draw(window); bool done = false; while (!done) { int keyCode = get_input(); switch (keyCode) { case KEY_PLUS: case KEY_EQUAL: IncGamma(); break; case KEY_MINUS: case KEY_UNDERSCORE: DecGamma(); break; default: if (keyCode != -1 && keyCode != -2) { done = true; } if (game_user_wants_to_quit != 0) { done = true; } } } if (!a1) { tile_refresh_display(); } win_delete(window); for (int index = 0; index < PAUSE_WINDOW_FRM_COUNT; index++) { art_ptr_unlock(frmHandles[index]); } message_exit(&optn_msgfl); if (!a1) { if (gameMouseWasVisible) { gmouse_3d_on(); } if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); } text_font(fontsave); return 0; } // 0x490748 static void ShadeScreen(bool a1) { if (a1) { mouse_hide(); } else { mouse_hide(); tile_refresh_display(); int windowWidth = 640; int windowHeight = win_height(display_win); unsigned char* windowBuffer = win_get_buf(display_win); grey_buf(windowBuffer, windowWidth, windowHeight, windowWidth); win_draw(display_win); } mouse_show(); } // 0x490798 static int do_prefscreen() { if (PrefStart() == -1) { debug_printf("\nPREFERENCE MENU: Error loading preference dialog data!\n"); return -1; } int rc = -1; while (rc == -1) { int eventCode = get_input(); switch (eventCode) { case KEY_RETURN: case KEY_UPPERCASE_P: case KEY_LOWERCASE_P: gsound_play_sfx_file("ib1p1xx1"); // FALLTHROUGH case 504: rc = 1; break; case KEY_CTRL_Q: case KEY_CTRL_X: case KEY_F10: game_quit_with_confirm(); break; case KEY_EQUAL: case KEY_PLUS: IncGamma(); break; case KEY_MINUS: case KEY_UNDERSCORE: DecGamma(); break; case KEY_F12: dump_screen(); break; case 527: SetDefaults(true); break; default: if (eventCode == KEY_ESCAPE || eventCode == 528 || game_user_wants_to_quit != 0) { RestoreSettings(); rc = 0; } else if (eventCode >= 505 && eventCode <= 524) { DoThing(eventCode); } break; } } PrefEnd(); return rc; } // 0x4908A0 static int PrefStart() { int i; int fid; char* messageItemText; int x; int y; int width; int height; int messageItemId; int btn; SaveSettings(); for (i = 0; i < PREFERENCES_WINDOW_FRM_COUNT; i++) { fid = art_id(OBJ_TYPE_INTERFACE, prfgrphs[i], 0, 0, 0); prfbmp[i] = art_lock(fid, &(grphkey2[i]), &(ginfo2[i].width), &(ginfo2[i].height)); if (prfbmp[i] == NULL) { for (; i != 0; i--) { art_ptr_unlock(grphkey2[i - 1]); } return -1; } } changed = false; int preferencesWindowX = 0; int preferencesWindowY = 0; prfwin = win_add(preferencesWindowX, preferencesWindowY, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_HEIGHT, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (prfwin == -1) { for (i = 0; i < PREFERENCES_WINDOW_FRM_COUNT; i++) { art_ptr_unlock(grphkey2[i]); } return -1; } prefbuf = win_get_buf(prfwin); memcpy(prefbuf, prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND], ginfo2[PREFERENCES_WINDOW_FRM_BACKGROUND].width * ginfo2[PREFERENCES_WINDOW_FRM_BACKGROUND].height); text_font(104); messageItemText = getmsg(&optn_msgfl, &optnmesg, 100); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 10 + 74, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); text_font(103); messageItemId = 101; for (i = 0; i < PRIMARY_PREF_COUNT; i++) { messageItemText = getmsg(&optn_msgfl, &optnmesg, messageItemId++); x = 99 - text_width(messageItemText) / 2; text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * row1Ytab[i] + x, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); } for (i = 0; i < SECONDARY_PREF_COUNT; i++) { messageItemText = getmsg(&optn_msgfl, &optnmesg, messageItemId++); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * row2Ytab[i] + 206, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); } for (i = 0; i < RANGE_PREF_COUNT; i++) { messageItemText = getmsg(&optn_msgfl, &optnmesg, messageItemId++); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * row3Ytab[i] + 384, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); } // DEFAULT messageItemText = getmsg(&optn_msgfl, &optnmesg, 120); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 449 + 43, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); // DONE messageItemText = getmsg(&optn_msgfl, &optnmesg, 4); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 449 + 169, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); // CANCEL messageItemText = getmsg(&optn_msgfl, &optnmesg, 121); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 449 + 283, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); // Affect player speed messageItemText = getmsg(&optn_msgfl, &optnmesg, 122); text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * 72 + 405, messageItemText, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); for (i = 0; i < PREF_COUNT; i++) { UpdateThing(i); } for (i = 0; i < PREF_COUNT; i++) { int mouseEnterEventCode; int mouseExitEventCode; int mouseDownEventCode; int mouseUpEventCode; if (i >= FIRST_RANGE_PREF) { x = 384; y = btndat[i].knobY - 12; width = 240; height = 23; mouseEnterEventCode = 526; mouseExitEventCode = 526; mouseDownEventCode = 505 + i; mouseUpEventCode = 526; } else if (i >= FIRST_SECONDARY_PREF) { x = btndat[i].minX; y = btndat[i].knobY - 5; width = btndat[i].maxX - x; height = 28; mouseEnterEventCode = -1; mouseExitEventCode = -1; mouseDownEventCode = -1; mouseUpEventCode = 505 + i; } else { x = btndat[i].minX; y = btndat[i].knobY - 4; width = btndat[i].maxX - x; height = 48; mouseEnterEventCode = -1; mouseExitEventCode = -1; mouseDownEventCode = -1; mouseUpEventCode = 505 + i; } btndat[i].btn = win_register_button(prfwin, x, y, width, height, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, NULL, NULL, NULL, 32); } plyrspdbid = win_register_button(prfwin, 383, 68, ginfo2[PREFERENCES_WINDOW_FRM_CHECKBOX_OFF].width, ginfo2[PREFERENCES_WINDOW_FRM_CHECKBOX_ON].height, -1, -1, 524, 524, prfbmp[PREFERENCES_WINDOW_FRM_CHECKBOX_OFF], prfbmp[PREFERENCES_WINDOW_FRM_CHECKBOX_ON], NULL, BUTTON_FLAG_TRANSPARENT | BUTTON_FLAG_0x01 | BUTTON_FLAG_0x02); if (plyrspdbid != -1) { win_set_button_rest_state(plyrspdbid, player_speedup, 0); } win_register_button_sound_func(plyrspdbid, gsound_med_butt_press, gsound_med_butt_press); // DEFAULT btn = win_register_button(prfwin, 23, 450, ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height, -1, -1, -1, 527, prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP], prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // DONE btn = win_register_button(prfwin, 148, 450, ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height, -1, -1, -1, 504, prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP], prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } // CANCEL btn = win_register_button(prfwin, 263, 450, ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP].width, ginfo2[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN].height, -1, -1, -1, 528, prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_UP], prfbmp[PREFERENCES_WINDOW_FRM_LITTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } text_font(101); win_draw(prfwin); return 0; } // 0x490E8C static void DoThing(int eventCode) { int x; int y; mouse_get_position(&x, &y); // This preference index also contains out-of-bounds value 19, // which is the only preference expressed as checkbox. int preferenceIndex = eventCode - 505; if (preferenceIndex >= FIRST_PRIMARY_PREF && preferenceIndex <= LAST_PRIMARY_PREF) { PreferenceDescription* meta = &(btndat[preferenceIndex]); int* valuePtr = meta->valuePtr; int value = *valuePtr; bool valueChanged = false; int v1 = meta->knobX + 23; int v2 = meta->knobY + 21; if (sqrt(pow((double)x - (double)v1, 2) + pow((double)y - (double)v2, 2)) > 16.0) { if (y > meta->knobY) { int v14 = meta->knobY + bglby[0]; if (y >= v14 && y <= v14 + text_height()) { if (x >= meta->minX && x <= meta->knobX) { *valuePtr = 0; meta->direction = 0; valueChanged = true; } else { if (meta->valuesCount >= 3 && x >= meta->knobX + bglbx[2] && x <= meta->maxX) { *valuePtr = 2; meta->direction = 0; valueChanged = true; } } } } else { if (x >= meta->knobX + 9 && x <= meta->knobX + 37) { *valuePtr = 1; if (value != 0) { meta->direction = 1; } else { meta->direction = 0; } valueChanged = true; } } if (meta->valuesCount == 4) { int v19 = meta->knobY + bglby[3]; if (y >= v19 && y <= v19 + 2 * text_height() && x >= meta->knobX + bglbx[3] && x <= meta->maxX) { *valuePtr = 3; meta->direction = 1; valueChanged = true; } } } else { if (meta->direction != 0) { if (value == 0) { meta->direction = 0; } } else { if (value == meta->valuesCount - 1) { meta->direction = 1; } } if (meta->direction != 0) { *valuePtr = value - 1; } else { *valuePtr = value + 1; } valueChanged = true; } if (valueChanged) { gsound_play_sfx_file("ib3p1xx1"); block_for_tocks(70); gsound_play_sfx_file("ib3lu1x1"); UpdateThing(preferenceIndex); win_draw(prfwin); changed = true; return; } } else if (preferenceIndex >= FIRST_SECONDARY_PREF && preferenceIndex <= LAST_SECONDARY_PREF) { PreferenceDescription* meta = &(btndat[preferenceIndex]); int* valuePtr = meta->valuePtr; int value = *valuePtr; bool valueChanged = false; int v1 = meta->knobX + 11; int v2 = meta->knobY + 12; if (sqrt(pow((double)x - (double)v1, 2) + pow((double)y - (double)v2, 2)) > 10.0) { int v23 = meta->knobY - 5; if (y >= v23 && y <= v23 + text_height() + 2) { if (x >= meta->minX && x <= meta->knobX) { *valuePtr = preferenceIndex == PREF_COMBAT_MESSAGES ? 1 : 0; valueChanged = true; } else if (x >= meta->knobX + 22.0 && x <= meta->maxX) { *valuePtr = preferenceIndex == PREF_COMBAT_MESSAGES ? 0 : 1; valueChanged = true; } } } else { *valuePtr ^= 1; valueChanged = true; } if (valueChanged) { gsound_play_sfx_file("ib2p1xx1"); block_for_tocks(70); gsound_play_sfx_file("ib2lu1x1"); UpdateThing(preferenceIndex); win_draw(prfwin); changed = true; return; } } else if (preferenceIndex >= FIRST_RANGE_PREF && preferenceIndex <= LAST_RANGE_PREF) { PreferenceDescription* meta = &(btndat[preferenceIndex]); int* valuePtr = meta->valuePtr; gsound_play_sfx_file("ib1p1xx1"); double value; switch (preferenceIndex) { case PREF_TEXT_BASE_DELAY: value = 6.0 - text_delay + 1.0; break; case PREF_BRIGHTNESS: value = gamma_value; break; case PREF_MOUSE_SENSITIVIY: value = mouse_sens; break; default: value = *valuePtr; break; } int knobX = (int)(219.0 / (meta->maxValue - meta->minValue)); int v31 = (int)((value - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0); buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + PREFERENCES_WINDOW_WIDTH * meta->knobY + 384, 240, 12, PREFERENCES_WINDOW_WIDTH, prefbuf + PREFERENCES_WINDOW_WIDTH * meta->knobY + 384, PREFERENCES_WINDOW_WIDTH); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_ON], 21, 12, 21, prefbuf + PREFERENCES_WINDOW_WIDTH * meta->knobY + v31, PREFERENCES_WINDOW_WIDTH); win_draw(prfwin); int sfxVolumeExample = 0; int speechVolumeExample = 0; while (true) { get_input(); int tick = get_time(); mouse_get_position(&x, &y); if (mouse_get_buttons() & 0x10) { gsound_play_sfx_file("ib1lu1x1"); UpdateThing(preferenceIndex); win_draw(prfwin); changed = true; return; } if (v31 + 14 > x) { if (v31 + 6 > x) { v31 = x - 6; if (v31 < 384) { v31 = 384; } } } else { v31 = x - 6; if (v31 > 603) { v31 = 603; } } double newValue = ((double)v31 - 384.0) / (219.0 / (meta->maxValue - meta->minValue)) + meta->minValue; int v52 = 0; switch (preferenceIndex) { case PREF_COMBAT_SPEED: *meta->valuePtr = (int)newValue; break; case PREF_TEXT_BASE_DELAY: text_delay = 6.0 - newValue + 1.0; break; case PREF_MASTER_VOLUME: *meta->valuePtr = (int)newValue; gsound_set_master_volume(master_volume); v52 = 1; break; case PREF_MUSIC_VOLUME: *meta->valuePtr = (int)newValue; gsound_background_volume_set(music_volume); v52 = 1; break; case PREF_SFX_VOLUME: *meta->valuePtr = (int)newValue; gsound_set_sfx_volume(sndfx_volume); v52 = 1; if (sfxVolumeExample == 0) { gsound_play_sfx_file("butin1"); sfxVolumeExample = 7; } else { sfxVolumeExample--; } break; case PREF_SPEECH_VOLUME: *meta->valuePtr = (int)newValue; gsound_speech_volume_set(speech_volume); v52 = 1; if (speechVolumeExample == 0) { gsound_speech_play("narrator\\options", 12, 13, 15); speechVolumeExample = 40; } else { speechVolumeExample--; } break; case PREF_BRIGHTNESS: gamma_value = newValue; colorGamma(newValue); break; case PREF_MOUSE_SENSITIVIY: mouse_sens = newValue; break; } if (v52) { int off = PREFERENCES_WINDOW_WIDTH * (meta->knobY - 12) + 384; buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + off, 240, 24, PREFERENCES_WINDOW_WIDTH, prefbuf + off, PREFERENCES_WINDOW_WIDTH); for (int optionIndex = 0; optionIndex < meta->valuesCount; optionIndex++) { const char* str = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[optionIndex]); int x; switch (optionIndex) { case 0: // 0x4926AA x = 384; // TODO: Incomplete. break; case 1: // 0x4926F3 switch (meta->valuesCount) { case 2: x = 624 - text_width(str); break; case 3: // This code path does not use floating-point arithmetic x = 504 - text_width(str) / 2 - 2; break; case 4: // Uses floating-point arithmetic x = 444 + text_width(str) / 2 - 8; break; } break; case 2: // 0x492766 switch (meta->valuesCount) { case 3: x = 624 - text_width(str); break; case 4: // Uses floating-point arithmetic x = 564 - text_width(str) - 4; break; } break; case 3: // 0x49279E x = 624 - text_width(str); break; } text_to_buf(prefbuf + PREFERENCES_WINDOW_WIDTH * (meta->knobY - 12) + x, str, PREFERENCES_WINDOW_WIDTH, PREFERENCES_WINDOW_WIDTH, colorTable[18979]); } } else { int off = PREFERENCES_WINDOW_WIDTH * meta->knobY + 384; buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + off, 240, 12, PREFERENCES_WINDOW_WIDTH, prefbuf + off, PREFERENCES_WINDOW_WIDTH); } trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_ON], 21, 12, 21, prefbuf + PREFERENCES_WINDOW_WIDTH * meta->knobY + v31, PREFERENCES_WINDOW_WIDTH); win_draw(prfwin); while (elapsed_time(tick) < 35) ; } } else if (preferenceIndex == 19) { player_speedup ^= 1; } changed = true; } // 0x491A68 static void UpdateThing(int index) { text_font(101); PreferenceDescription* meta = &(btndat[index]); if (index >= FIRST_PRIMARY_PREF && index <= LAST_PRIMARY_PREF) { // 0x48FC1C static const int offsets[PRIMARY_PREF_COUNT] = { 66, // game difficulty 143, // combat difficulty 222, // violence level 304, // target highlight 382, // combat looks }; int primaryOptionIndex = index - FIRST_PRIMARY_PREF; buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * offsets[primaryOptionIndex] + 23, 160, 54, 640, prefbuf + 640 * offsets[primaryOptionIndex] + 23, 640); for (int valueIndex = 0; valueIndex < meta->valuesCount; valueIndex++) { const char* text = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[valueIndex]); char copy[100]; // TODO: Size is probably wrong. strcpy(copy, text); int x = meta->knobX + bglbx[valueIndex]; int len = text_width(copy); switch (valueIndex) { case 0: x -= text_width(copy); meta->minX = x; break; case 1: x -= len / 2; meta->maxX = x + len; break; case 2: case 3: meta->maxX = x + len; break; } char* p = copy; while (*p != '\0' && *p != ' ') { p++; } int y = meta->knobY + bglby[valueIndex]; const char* s; if (*p != '\0') { *p = '\0'; text_to_buf(prefbuf + 640 * y + x, copy, 640, 640, colorTable[18979]); s = p + 1; y += text_height(); } else { s = copy; } text_to_buf(prefbuf + 640 * y + x, s, 640, 640, colorTable[18979]); } int value = *(meta->valuePtr); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_PRIMARY_SWITCH] + (46 * 47) * value, 46, 47, 46, prefbuf + 640 * meta->knobY + meta->knobX, 640); } else if (index >= FIRST_SECONDARY_PREF && index <= LAST_SECONDARY_PREF) { // 0x48FC30 static const int offsets[SECONDARY_PREF_COUNT] = { 66, // combat messages 133, // combat taunts 200, // language filter 264, // running 331, // subtitles 397, // item highlight }; int secondaryOptionIndex = index - FIRST_SECONDARY_PREF; buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * offsets[secondaryOptionIndex] + 251, 113, 34, 640, prefbuf + 640 * offsets[secondaryOptionIndex] + 251, 640); // Secondary options are booleans, so it's index is also it's value. for (int value = 0; value < 2; value++) { const char* text = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[value]); int x; if (value) { x = meta->knobX + smlbx[value]; meta->maxX = x + text_width(text); } else { x = meta->knobX + smlbx[value] - text_width(text); meta->minX = x; } text_to_buf(prefbuf + 640 * (meta->knobY - 5) + x, text, 640, 640, colorTable[18979]); } int value = *(meta->valuePtr); if (index == PREF_COMBAT_MESSAGES) { value ^= 1; } trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_SECONDARY_SWITCH] + (22 * 25) * value, 22, 25, 22, prefbuf + 640 * meta->knobY + meta->knobX, 640); } else if (index >= FIRST_RANGE_PREF && index <= LAST_RANGE_PREF) { buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_BACKGROUND] + 640 * (meta->knobY - 12) + 384, 240, 24, 640, prefbuf + 640 * (meta->knobY - 12) + 384, 640); switch (index) { case PREF_COMBAT_SPEED: if (1) { double value = *meta->valuePtr; value = min(max(value, 0.0), 50.0); int x = (int)((value - meta->minValue) * 219.0 / (meta->maxValue - meta->minValue) + 384.0); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640); } break; case PREF_TEXT_BASE_DELAY: if (1) { text_delay = min(max(text_delay, 1.0), 6.0); int x = (int)((6.0 - text_delay) * 43.8 + 384.0); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640); double value = (text_delay - 1.0) * 0.2 * 2.0; value = min(max(value, 0.0), 2.0); text_object_set_base_delay(text_delay); text_object_set_line_delay(value); } break; case PREF_MASTER_VOLUME: case PREF_MUSIC_VOLUME: case PREF_SFX_VOLUME: case PREF_SPEECH_VOLUME: if (1) { double value = *meta->valuePtr; value = min(max(value, meta->minValue), meta->maxValue); int x = (int)((value - meta->minValue) * 219.0 / (meta->maxValue - meta->minValue) + 384.0); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640); switch (index) { case PREF_MASTER_VOLUME: gsound_set_master_volume(master_volume); break; case PREF_MUSIC_VOLUME: gsound_background_volume_set(music_volume); break; case PREF_SFX_VOLUME: gsound_set_sfx_volume(sndfx_volume); break; case PREF_SPEECH_VOLUME: gsound_speech_volume_set(speech_volume); break; } } break; case PREF_BRIGHTNESS: if (1) { gamma_value = min(max(gamma_value, 1.0), 1.17999267578125); int x = (int)((gamma_value - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640); colorGamma(gamma_value); } break; case PREF_MOUSE_SENSITIVIY: if (1) { mouse_sens = min(max(mouse_sens, 1.0), 2.5); int x = (int)((mouse_sens - meta->minValue) * (219.0 / (meta->maxValue - meta->minValue)) + 384.0); trans_buf_to_buf(prfbmp[PREFERENCES_WINDOW_FRM_KNOB_OFF], 21, 12, 21, prefbuf + 640 * meta->knobY + x, 640); mouse_set_sensitivity(mouse_sens); } break; } for (int optionIndex = 0; optionIndex < meta->valuesCount; optionIndex++) { const char* str = getmsg(&optn_msgfl, &optnmesg, meta->labelIds[optionIndex]); int x; switch (optionIndex) { case 0: // 0x4926AA x = 384; // TODO: Incomplete. break; case 1: // 0x4926F3 switch (meta->valuesCount) { case 2: x = 624 - text_width(str); break; case 3: // This code path does not use floating-point arithmetic x = 504 - text_width(str) / 2 - 2; break; case 4: // Uses floating-point arithmetic x = 444 + text_width(str) / 2 - 8; break; } break; case 2: // 0x492766 switch (meta->valuesCount) { case 3: x = 624 - text_width(str); break; case 4: // Uses floating-point arithmetic x = 564 - text_width(str) - 4; break; } break; case 3: // 0x49279E x = 624 - text_width(str); break; } text_to_buf(prefbuf + 640 * (meta->knobY - 12) + x, str, 640, 640, colorTable[18979]); } } else { // return false; } // TODO: Incomplete. // return true; } // 0x492870 static int PrefEnd() { if (changed) { SavePrefs(1); JustUpdate(); combat_highlight_change(); } win_delete(prfwin); for (int index = 0; index < PREFERENCES_WINDOW_FRM_COUNT; index++) { art_ptr_unlock(grphkey2[index]); } return 0; } // 0x4928B8 int init_options_menu() { for (int index = 0; index < 11; index++) { btndat[index].direction = 0; } SetSystemPrefs(); InitGreyTable(0, 255); return 0; } // 0x4928E4 void IncGamma() { gamma_value = GAMMA_MIN; config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gamma_value); if (gamma_value < GAMMA_MAX) { gamma_value += GAMMA_STEP; if (gamma_value >= GAMMA_MIN) { if (gamma_value > GAMMA_MAX) { gamma_value = GAMMA_MAX; } } else { gamma_value = GAMMA_MIN; } colorGamma(gamma_value); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gamma_value); gconfig_save(); } } // 0x4929C8 void DecGamma() { gamma_value = GAMMA_MIN; config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gamma_value); if (gamma_value > GAMMA_MIN) { gamma_value -= GAMMA_STEP; if (gamma_value >= GAMMA_MIN) { if (gamma_value > GAMMA_MAX) { gamma_value = GAMMA_MAX; } } else { gamma_value = GAMMA_MIN; } colorGamma(gamma_value); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gamma_value); gconfig_save(); } } // 0x492AA8 static void SetSystemPrefs() { SetDefaults(false); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &game_difficulty); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combat_difficulty); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violence_level); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, &target_highlight); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, &combat_messages); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, &combatLookValue); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, &combat_taunts); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &language_filter); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, &prf_running); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, &subtitles); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, &item_highlight); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, &combat_speed); config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &text_delay); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, &player_speedup); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, &master_volume); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, &music_volume); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, &sndfx_volume); config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, &speech_volume); config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, &gamma_value); config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, &mouse_sens); JustUpdate(); } // 0x492CB0 static int SavePrefs(bool save) { config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, game_difficulty); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, combat_difficulty); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, violence_level); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TARGET_HIGHLIGHT_KEY, target_highlight); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_MESSAGES_KEY, combat_messages); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_LOOKS_KEY, combatLookValue); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_TAUNTS_KEY, combat_taunts); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, language_filter); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_KEY, prf_running); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_SUBTITLES_KEY, subtitles); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_ITEM_HIGHLIGHT_KEY, item_highlight); config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_SPEED_KEY, combat_speed); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, text_delay); double textLineDelay = (text_delay - 1.0) / 5.0 * 2.0; if (textLineDelay >= 0.0) { if (textLineDelay > 2.0) { textLineDelay = 2.0; } config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, textLineDelay); } else { config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, 0.0); } config_set_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_PLAYER_SPEEDUP_KEY, player_speedup); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MASTER_VOLUME_KEY, master_volume); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_MUSIC_VOLUME_KEY, music_volume); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SNDFX_VOLUME_KEY, sndfx_volume); config_set_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_SPEECH_VOLUME_KEY, speech_volume); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_BRIGHTNESS_KEY, gamma_value); config_set_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_MOUSE_SENSITIVITY_KEY, mouse_sens); if (save) { gconfig_save(); } return 0; } // 0x492F60 static void SetDefaults(bool a1) { combat_difficulty = COMBAT_DIFFICULTY_NORMAL; violence_level = VIOLENCE_LEVEL_MAXIMUM_BLOOD; target_highlight = TARGET_HIGHLIGHT_TARGETING_ONLY; combat_messages = 1; combatLookValue = 0; combat_taunts = 1; prf_running = 0; subtitles = 0; item_highlight = 1; combat_speed = 0; player_speedup = 0; text_delay = 3.5; gamma_value = 1.0; mouse_sens = 1.0; game_difficulty = 1; language_filter = 0; master_volume = 22281; music_volume = 22281; sndfx_volume = 22281; speech_volume = 22281; if (a1) { for (int index = 0; index < PREF_COUNT; index++) { UpdateThing(index); } win_set_button_rest_state(plyrspdbid, player_speedup, 0); win_draw(prfwin); changed = true; } } // 0x493054 static void SaveSettings() { settings_backup[PREF_GAME_DIFFICULTY] = game_difficulty; settings_backup[PREF_COMBAT_DIFFICULTY] = combat_difficulty; settings_backup[PREF_VIOLENCE_LEVEL] = violence_level; settings_backup[PREF_TARGET_HIGHLIGHT] = target_highlight; settings_backup[PREF_COMBAT_LOOKS] = combatLookValue; settings_backup[PREF_COMBAT_MESSAGES] = combat_messages; settings_backup[PREF_COMBAT_TAUNTS] = combat_taunts; settings_backup[PREF_LANGUAGE_FILTER] = language_filter; settings_backup[PREF_RUNNING] = prf_running; settings_backup[PREF_SUBTITLES] = subtitles; settings_backup[PREF_ITEM_HIGHLIGHT] = item_highlight; settings_backup[PREF_COMBAT_SPEED] = combat_speed; settings_backup[PREF_TEXT_BASE_DELAY] = player_speedup; settings_backup[PREF_MASTER_VOLUME] = master_volume; text_delay_back = text_delay; settings_backup[PREF_MUSIC_VOLUME] = music_volume; gamma_value_back = gamma_value; settings_backup[PREF_SFX_VOLUME] = sndfx_volume; mouse_sens_back = mouse_sens; settings_backup[PREF_SPEECH_VOLUME] = speech_volume; } // 0x493128 static void RestoreSettings() { game_difficulty = settings_backup[PREF_GAME_DIFFICULTY]; combat_difficulty = settings_backup[PREF_COMBAT_DIFFICULTY]; violence_level = settings_backup[PREF_VIOLENCE_LEVEL]; target_highlight = settings_backup[PREF_TARGET_HIGHLIGHT]; combatLookValue = settings_backup[PREF_COMBAT_LOOKS]; combat_messages = settings_backup[PREF_COMBAT_MESSAGES]; combat_taunts = settings_backup[PREF_COMBAT_TAUNTS]; language_filter = settings_backup[PREF_LANGUAGE_FILTER]; prf_running = settings_backup[PREF_RUNNING]; subtitles = settings_backup[PREF_SUBTITLES]; item_highlight = settings_backup[PREF_ITEM_HIGHLIGHT]; combat_speed = settings_backup[PREF_COMBAT_SPEED]; player_speedup = settings_backup[PREF_TEXT_BASE_DELAY]; master_volume = settings_backup[PREF_MASTER_VOLUME]; text_delay = text_delay_back; music_volume = settings_backup[PREF_MUSIC_VOLUME]; gamma_value = gamma_value_back; sndfx_volume = settings_backup[PREF_SFX_VOLUME]; mouse_sens = mouse_sens_back; speech_volume = settings_backup[PREF_SPEECH_VOLUME]; JustUpdate(); } // 0x4931F8 static void JustUpdate() { game_difficulty = min(max(game_difficulty, 0), 2); combat_difficulty = min(max(combat_difficulty, 0), 2); violence_level = min(max(violence_level, 0), 3); target_highlight = min(max(target_highlight, 0), 2); combat_messages = min(max(combat_messages, 0), 1); combatLookValue = min(max(combatLookValue, 0), 1); combat_taunts = min(max(combat_taunts, 0), 1); language_filter = min(max(language_filter, 0), 1); prf_running = min(max(prf_running, 0), 1); subtitles = min(max(subtitles, 0), 1); item_highlight = min(max(item_highlight, 0), 1); combat_speed = min(max(combat_speed, 0), 50); player_speedup = min(max(player_speedup, 0), 1); text_delay = min(max(text_delay, 1.0), 6.0); master_volume = min(max(master_volume, 0), VOLUME_MAX); music_volume = min(max(music_volume, 0), VOLUME_MAX); sndfx_volume = min(max(sndfx_volume, 0), VOLUME_MAX); speech_volume = min(max(speech_volume, 0), VOLUME_MAX); gamma_value = min(max(gamma_value, 1.0), 1.17999267578125); mouse_sens = min(max(mouse_sens, 1.0), 2.5); text_object_set_base_delay(text_delay); gmouse_3d_synch_item_highlight(); double textLineDelay = (text_delay + (-1.0)) * 0.2 * 2.0; textLineDelay = min(max(textLineDelay, 0.0), 2.0); text_object_set_line_delay(textLineDelay); combatai_refresh_messages(); scr_message_free(); gsound_set_master_volume(master_volume); gsound_background_volume_set(music_volume); gsound_set_sfx_volume(sndfx_volume); gsound_speech_volume_set(speech_volume); mouse_set_sensitivity(mouse_sens); colorGamma(gamma_value); } // 0x493224 int save_options(File* stream) { float textBaseDelay = (float)text_delay; float brightness = (float)gamma_value; float mouseSensitivity = (float)mouse_sens; if (db_fwriteInt(stream, game_difficulty) == -1) goto err; if (db_fwriteInt(stream, combat_difficulty) == -1) goto err; if (db_fwriteInt(stream, violence_level) == -1) goto err; if (db_fwriteInt(stream, target_highlight) == -1) goto err; if (db_fwriteInt(stream, combatLookValue) == -1) goto err; if (db_fwriteInt(stream, combat_messages) == -1) goto err; if (db_fwriteInt(stream, combat_taunts) == -1) goto err; if (db_fwriteInt(stream, language_filter) == -1) goto err; if (db_fwriteInt(stream, prf_running) == -1) goto err; if (db_fwriteInt(stream, subtitles) == -1) goto err; if (db_fwriteInt(stream, item_highlight) == -1) goto err; if (db_fwriteInt(stream, combat_speed) == -1) goto err; if (db_fwriteInt(stream, player_speedup) == -1) goto err; if (db_fwriteFloat(stream, textBaseDelay) == -1) goto err; if (db_fwriteInt(stream, master_volume) == -1) goto err; if (db_fwriteInt(stream, music_volume) == -1) goto err; if (db_fwriteInt(stream, sndfx_volume) == -1) goto err; if (db_fwriteInt(stream, speech_volume) == -1) goto err; if (db_fwriteFloat(stream, brightness) == -1) goto err; if (db_fwriteFloat(stream, mouseSensitivity) == -1) goto err; return 0; err: debug_printf("\nOPTION MENU: Error save option data!\n"); return -1; } // 0x49340C int load_options(File* stream) { float textBaseDelay; float brightness; float mouseSensitivity; SetDefaults(false); if (db_freadInt(stream, &game_difficulty) == -1) goto err; if (db_freadInt(stream, &combat_difficulty) == -1) goto err; if (db_freadInt(stream, &violence_level) == -1) goto err; if (db_freadInt(stream, &target_highlight) == -1) goto err; if (db_freadInt(stream, &combatLookValue) == -1) goto err; if (db_freadInt(stream, &combat_messages) == -1) goto err; if (db_freadInt(stream, &combat_taunts) == -1) goto err; if (db_freadInt(stream, &language_filter) == -1) goto err; if (db_freadInt(stream, &prf_running) == -1) goto err; if (db_freadInt(stream, &subtitles) == -1) goto err; if (db_freadInt(stream, &item_highlight) == -1) goto err; if (db_freadInt(stream, &combat_speed) == -1) goto err; if (db_freadInt(stream, &player_speedup) == -1) goto err; if (db_freadFloat(stream, &textBaseDelay) == -1) goto err; if (db_freadInt(stream, &master_volume) == -1) goto err; if (db_freadInt(stream, &music_volume) == -1) goto err; if (db_freadInt(stream, &sndfx_volume) == -1) goto err; if (db_freadInt(stream, &speech_volume) == -1) goto err; if (db_freadFloat(stream, &brightness) == -1) goto err; if (db_freadFloat(stream, &mouseSensitivity) == -1) goto err; gamma_value = brightness; mouse_sens = mouseSensitivity; text_delay = textBaseDelay; JustUpdate(); SavePrefs(0); return 0; err: debug_printf("\nOPTION MENU: Error loading option data!, using defaults.\n"); SetDefaults(false); JustUpdate(); SavePrefs(0); return -1; } ================================================ FILE: src/game/options.h ================================================ #ifndef FALLOUT_GAME_OPTIONS_H_ #define FALLOUT_GAME_OPTIONS_H_ #include #include "plib/db/db.h" int do_options(); int do_optionsFunc(int initialKeyCode); int PauseWindow(bool a1); int init_options_menu(); int save_options(File* stream); int load_options(File* stream); void IncGamma(); void DecGamma(); #endif /* FALLOUT_GAME_OPTIONS_H_ */ ================================================ FILE: src/game/palette.c ================================================ #include "game/palette.h" #include #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/cycle.h" #include "plib/gnw/debug.h" #include "game/gsound.h" // 0x6639D0 static unsigned char current_palette[256 * 3]; // 0x663CD0 unsigned char white_palette[256 * 3]; // 0x663FD0 unsigned char black_palette[256 * 3]; // 0x6642D0 static int fade_steps; // 0x493A00 void palette_init() { memset(black_palette, 0, 256 * 3); memset(white_palette, 63, 256 * 3); memcpy(current_palette, cmap, 256 * 3); unsigned int tick = get_time(); if (gsound_background_is_enabled() || gsound_speech_is_enabled()) { colorSetFadeBkFunc(soundContinueAll); } fadeSystemPalette(current_palette, current_palette, 60); colorSetFadeBkFunc(NULL); unsigned int diff = elapsed_time(tick); // NOTE: Modern CPUs are super fast, so it's possible that less than 10ms // (the resolution of underlying GetTicks) is needed to fade between two // palettes, which leads to zero diff, which in turn leads to unpredictable // number of fade steps. To fix that the fallback value is used (46). This // value is commonly seen when running the game in 1 core VM. if (diff == 0) { diff = 46; } fade_steps = (int)(60.0 / (diff * (1.0 / 700.0))); debug_printf("\nFade time is %u\nFade steps are %d\n", diff, fade_steps); } // NOTE: Uncollapsed 0x493AD0. void palette_reset() { } // NOTE: Uncollapsed 0x493AD0. void palette_exit() { } // 0x493AD4 void palette_fade_to(unsigned char* palette) { bool colorCycleWasEnabled = cycle_is_enabled(); cycle_disable(); if (gsound_background_is_enabled() || gsound_speech_is_enabled()) { colorSetFadeBkFunc(soundContinueAll); } fadeSystemPalette(current_palette, palette, fade_steps); colorSetFadeBkFunc(NULL); memcpy(current_palette, palette, 768); if (colorCycleWasEnabled) { cycle_enable(); } } // 0x493B48 void palette_set_to(unsigned char* palette) { memcpy(current_palette, palette, sizeof(current_palette)); setSystemPalette(palette); } // 0x493B78 void palette_set_entries(unsigned char* palette, int start, int end) { memcpy(current_palette + 3 * start, palette, 3 * (end - start + 1)); setSystemPaletteEntries(palette, start, end); } ================================================ FILE: src/game/palette.h ================================================ #ifndef FALLOUT_GAME_PALETTE_H_ #define FALLOUT_GAME_PALETTE_H_ extern unsigned char white_palette[256 * 3]; extern unsigned char black_palette[256 * 3]; void palette_init(); void _palette_reset_(); void palette_reset(); void palette_exit(); void palette_fade_to(unsigned char* palette); void palette_set_to(unsigned char* palette); void palette_set_entries(unsigned char* palette, int start, int end); #endif /* FALLOUT_GAME_PALETTE_H_ */ ================================================ FILE: src/game/party.c ================================================ #include "game/party.h" #include #include #include "game/anim.h" #include "plib/color/color.h" #include "game/combatai.h" #include "game/config.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/gdialog.h" #include "game/item.h" #include "game/loadsave.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/proto.h" #include "game/protinst.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/strparse.h" #include "game/textobj.h" #include "game/tile.h" #include "plib/gnw/gnw.h" typedef struct PartyMemberAI { bool area_attack_mode[AREA_ATTACK_MODE_COUNT]; bool run_away_mode[RUN_AWAY_MODE_COUNT]; bool best_weapon[BEST_WEAPON_COUNT]; bool distance_mode[DISTANCE_COUNT]; bool attack_who[ATTACK_WHO_COUNT]; bool chem_use[CHEM_USE_COUNT]; bool disposition[DISPOSITION_COUNT]; int level_minimum; int level_up_every; int level_pids_num; int level_pids[5]; } PartyMemberAI; // TODO: Rename fields. typedef struct PartyMemberLevelUpInfo { int field_0; int field_4; // party member level int field_8; // early what? } PartyMemberLevelUpInfo; // TODO: Not sure if the same struct is used in `itemSaveListHead` and // `partyMemberList`. typedef struct PartyMember { Object* object; Script* script; int* vars; struct PartyMember* next; } PartyMember; static int partyMemberGetAIOptions(Object* object, PartyMemberAI** aiOptionsPtr); static void partyMemberAISlotInit(PartyMemberAI* aiOptions); static int partyMemberSlotInit(int index); static int partyMemberPrepLoadInstance(PartyMember* partyMember); static int partyMemberRecoverLoadInstance(PartyMember* partyMember); static int partyMemberNewObjID(); static int partyMemberNewObjIDRecurseFind(Object* object, int objectId); static int partyMemberPrepItemSave(Object* object); static int partyMemberItemSave(Object* object); static int partyMemberItemRecover(PartyMember* partyMember); static int partyMemberClearItemList(); static int partyFixMultipleMembers(); static int partyMemberCopyLevelInfo(Object* object, int a2); // 0x519D9C int partyMemberMaxCount = 0; // 0x519DA0 int* partyMemberPidList = NULL; // 0x519DA4 PartyMember* itemSaveListHead = NULL; // List of party members, it's length is [partyMemberMaxCount] + 20. // // 0x519DA8 static PartyMember* partyMemberList = NULL; // Number of critters added to party. // // 0x519DAC static int partyMemberCount = 0; // 0x519DB0 static int partyMemberItemCount = 20000; // 0x519DB4 static int partyStatePrepped = 0; // 0x519DB8 static PartyMemberAI* partyMemberAIOptions = NULL; // 0x519DBC static PartyMemberLevelUpInfo* partyMemberLevelUpInfoList = NULL; // 0x493BC0 int partyMember_init() { Config config; partyMemberMaxCount = 0; if (!config_init(&config)) { return -1; } if (!config_load(&config, "data\\party.txt", true)) { goto err; } char section[50]; sprintf(section, "Party Member %d", partyMemberMaxCount); int partyMemberPid; while (config_get_value(&config, section, "party_member_pid", &partyMemberPid)) { partyMemberMaxCount++; sprintf(section, "Party Member %d", partyMemberMaxCount); } partyMemberPidList = (int*)mem_malloc(sizeof(*partyMemberPidList) * partyMemberMaxCount); if (partyMemberPidList == NULL) { goto err; } memset(partyMemberPidList, 0, sizeof(*partyMemberPidList) * partyMemberMaxCount); partyMemberList = (PartyMember*)mem_malloc(sizeof(*partyMemberList) * (partyMemberMaxCount + 20)); if (partyMemberList == NULL) { goto err; } memset(partyMemberList, 0, sizeof(*partyMemberList) * (partyMemberMaxCount + 20)); partyMemberAIOptions = (PartyMemberAI*)mem_malloc(sizeof(*partyMemberAIOptions) * partyMemberMaxCount); if (partyMemberAIOptions == NULL) { goto err; } memset(partyMemberAIOptions, 0, sizeof(*partyMemberAIOptions) * partyMemberMaxCount); partyMemberLevelUpInfoList = (PartyMemberLevelUpInfo*)mem_malloc(sizeof(*partyMemberLevelUpInfoList) * partyMemberMaxCount); if (partyMemberLevelUpInfoList == NULL) goto err; memset(partyMemberLevelUpInfoList, 0, sizeof(*partyMemberLevelUpInfoList) * partyMemberMaxCount); for (int index = 0; index < partyMemberMaxCount; index++) { sprintf(section, "Party Member %d", index); if (!config_get_value(&config, section, "party_member_pid", &partyMemberPid)) { break; } PartyMemberAI* aiOptions = &(partyMemberAIOptions[index]); partyMemberPidList[index] = partyMemberPid; partyMemberAISlotInit(aiOptions); char* string; if (config_get_string(&config, section, "area_attack_mode", &string)) { while (*string != '\0') { int areaAttackMode; strParseStrFromList(&string, &areaAttackMode, area_attack_mode_strs, AREA_ATTACK_MODE_COUNT); aiOptions->area_attack_mode[areaAttackMode] = true; } } if (config_get_string(&config, section, "attack_who", &string)) { while (*string != '\0') { int attachWho; strParseStrFromList(&string, &attachWho, attack_who_mode_strs, ATTACK_WHO_COUNT); aiOptions->attack_who[attachWho] = true; } } if (config_get_string(&config, section, "best_weapon", &string)) { while (*string != '\0') { int bestWeapon; strParseStrFromList(&string, &bestWeapon, weapon_pref_strs, BEST_WEAPON_COUNT); aiOptions->best_weapon[bestWeapon] = true; } } if (config_get_string(&config, section, "chem_use", &string)) { while (*string != '\0') { int chemUse; strParseStrFromList(&string, &chemUse, chem_use_mode_strs, CHEM_USE_COUNT); aiOptions->chem_use[chemUse] = true; } } if (config_get_string(&config, section, "distance", &string)) { while (*string != '\0') { int distanceMode; strParseStrFromList(&string, &distanceMode, distance_pref_strs, DISTANCE_COUNT); aiOptions->distance_mode[distanceMode] = true; } } if (config_get_string(&config, section, "run_away_mode", &string)) { while (*string != '\0') { int runAwayMode; strParseStrFromList(&string, &runAwayMode, run_away_mode_strs, RUN_AWAY_MODE_COUNT); aiOptions->run_away_mode[runAwayMode] = true; } } if (config_get_string(&config, section, "disposition", &string)) { while (*string != '\0') { int disposition; strParseStrFromList(&string, &disposition, disposition_strs, DISPOSITION_COUNT); aiOptions->disposition[disposition] = true; } } int levelUpEvery; if (config_get_value(&config, section, "level_up_every", &levelUpEvery)) { aiOptions->level_up_every = levelUpEvery; int levelMinimum; if (config_get_value(&config, section, "level_minimum", &levelMinimum)) { aiOptions->level_minimum = levelMinimum; } if (config_get_string(&config, section, "level_pids", &string)) { while (*string != '\0' && aiOptions->level_pids_num < 5) { int levelPid; strParseValue(&string, &levelPid); aiOptions->level_pids[aiOptions->level_pids_num] = levelPid; aiOptions->level_pids_num++; } } } } config_exit(&config); return 0; err: config_exit(&config); return -1; } // 0x4940E4 void partyMember_reset() { for (int index = 0; index < partyMemberMaxCount; index++) { // NOTE: Uninline. partyMemberSlotInit(index); } } // 0x494134 void partyMember_exit() { for (int index = 0; index < partyMemberMaxCount; index++) { // NOTE: Uninline. partyMemberSlotInit(index); } partyMemberMaxCount = 0; if (partyMemberPidList != NULL) { mem_free(partyMemberPidList); partyMemberPidList = NULL; } if (partyMemberList != NULL) { mem_free(partyMemberList); partyMemberList = NULL; } if (partyMemberAIOptions != NULL) { mem_free(partyMemberAIOptions); partyMemberAIOptions = NULL; } if (partyMemberLevelUpInfoList != NULL) { mem_free(partyMemberLevelUpInfoList); partyMemberLevelUpInfoList = NULL; } } // 0x4941F0 static int partyMemberGetAIOptions(Object* object, PartyMemberAI** aiOptionsPtr) { for (int index = 1; index < partyMemberMaxCount; index++) { if (partyMemberPidList[index] == object->pid) { *aiOptionsPtr = &(partyMemberAIOptions[index]); return 0; } } return -1; } // 0x49425C static void partyMemberAISlotInit(PartyMemberAI* aiOptions) { for (int index = 0; index < AREA_ATTACK_MODE_COUNT; index++) { aiOptions->area_attack_mode[index] = 0; } for (int index = 0; index < RUN_AWAY_MODE_COUNT; index++) { aiOptions->run_away_mode[index] = 0; } for (int index = 0; index < BEST_WEAPON_COUNT; index++) { aiOptions->best_weapon[index] = 0; } for (int index = 0; index < DISTANCE_COUNT; index++) { aiOptions->distance_mode[index] = 0; } for (int index = 0; index < ATTACK_WHO_COUNT; index++) { aiOptions->attack_who[index] = 0; } for (int index = 0; index < CHEM_USE_COUNT; index++) { aiOptions->chem_use[index] = 0; } for (int index = 0; index < DISPOSITION_COUNT; index++) { aiOptions->disposition[index] = 0; } aiOptions->level_minimum = 0; aiOptions->level_up_every = 0; aiOptions->level_pids_num = 0; aiOptions->level_pids[0] = -1; for (int index = 0; index < partyMemberMaxCount; index++) { // NOTE: Uninline. partyMemberSlotInit(index); } } // NOTE: Inlined. // // 0x494340 static int partyMemberSlotInit(int index) { if (index >= partyMemberMaxCount) { return -1; } partyMemberLevelUpInfoList[index].field_0 = 0; partyMemberLevelUpInfoList[index].field_4 = 0; partyMemberLevelUpInfoList[index].field_8 = 0; return 0; } // 0x494378 int partyMemberAdd(Object* object) { if (partyMemberCount >= partyMemberMaxCount + 20) { return -1; } for (int index = 0; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); if (partyMember->object == object || partyMember->object->pid == object->pid) { return 0; } } if (partyStatePrepped) { debug_printf("\npartyMemberAdd DENIED: %s\n", critter_name(object)); return -1; } PartyMember* partyMember = &(partyMemberList[partyMemberCount]); partyMember->object = object; partyMember->script = NULL; partyMember->vars = NULL; object->id = (object->pid & 0xFFFFFF) + 18000; object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); partyMemberCount++; Script* script; if (scr_ptr(object->sid, &script) != -1) { script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); script->field_1C = object->id; object->sid = ((object->pid & 0xFFFFFF) + 18000) | (object->sid & 0xFF000000); script->sid = object->sid; } combatai_switch_team(object, 0); queue_remove_this(object, EVENT_TYPE_SCRIPT); if (gdialogActive()) { if (object == dialog_target) { gdialogUpdatePartyStatus(); } } return 0; } // 0x4944DC int partyMemberRemove(Object* object) { if (partyMemberCount == 0) { return -1; } if (object == NULL) { return -1; } int index; for (index = 1; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); if (partyMember->object == object) { break; } } if (index == partyMemberCount) { return -1; } if (partyStatePrepped) { debug_printf("\npartyMemberRemove DENIED: %s\n", critter_name(object)); return -1; } if (index < partyMemberCount - 1) { partyMemberList[index].object = partyMemberList[partyMemberCount - 1].object; } object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); partyMemberCount--; Script* script; if (scr_ptr(object->sid, &script) != -1) { script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); } queue_remove_this(object, EVENT_TYPE_SCRIPT); if (gdialogActive()) { if (object == dialog_target) { gdialogUpdatePartyStatus(); } } return 0; } // 0x49460C int partyMemberPrepSave() { partyStatePrepped = 1; for (int index = 0; index < partyMemberCount; index++) { PartyMember* ptr = &(partyMemberList[index]); if (index > 0) { ptr->object->flags &= ~(OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); } Script* script; if (scr_ptr(ptr->object->sid, &script) != -1) { script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); } } return 0; } // 0x49466C int partyMemberUnPrepSave() { for (int index = 0; index < partyMemberCount; index++) { PartyMember* ptr = &(partyMemberList[index]); if (index > 0) { ptr->object->flags |= (OBJECT_FLAG_0x400 | OBJECT_TEMPORARY); } Script* script; if (scr_ptr(ptr->object->sid, &script) != -1) { script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); } } partyStatePrepped = 0; return 0; } // 0x4946CC int partyMemberSave(File* stream) { if (db_fwriteInt(stream, partyMemberCount) == -1) return -1; if (db_fwriteInt(stream, partyMemberItemCount) == -1) return -1; for (int index = 1; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); if (db_fwriteInt(stream, partyMember->object->id) == -1) return -1; } for (int index = 1; index < partyMemberMaxCount; index++) { PartyMemberLevelUpInfo* levelUpInfo = &(partyMemberLevelUpInfoList[index]); if (db_fwriteInt(stream, levelUpInfo->field_0) == -1) return -1; if (db_fwriteInt(stream, levelUpInfo->field_4) == -1) return -1; if (db_fwriteInt(stream, levelUpInfo->field_8) == -1) return -1; } return 0; } // 0x4947AC int partyMemberPrepLoad() { if (partyStatePrepped) { return -1; } partyStatePrepped = 1; for (int index = 0; index < partyMemberCount; index++) { PartyMember* ptr_519DA8 = &(partyMemberList[index]); if (partyMemberPrepLoadInstance(ptr_519DA8) != 0) { return -1; } } return 0; } // 0x49480C static int partyMemberPrepLoadInstance(PartyMember* partyMember) { Object* obj = partyMember->object; if (obj == NULL) { debug_printf("\n Error!: partyMemberPrepLoadInstance: No Critter Object!"); partyMember->script = NULL; partyMember->vars = NULL; partyMember->next = NULL; return 0; } if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { obj->data.critter.combat.whoHitMe = NULL; } Script* script; if (scr_ptr(obj->sid, &script) == -1) { debug_printf("\n Error!: partyMemberPrepLoadInstance: Can't find script!"); debug_printf("\n partyMemberPrepLoadInstance: script was: (%s)", critter_name(obj)); partyMember->script = NULL; partyMember->vars = NULL; partyMember->next = NULL; return 0; } partyMember->script = (Script*)mem_malloc(sizeof(*script)); if (partyMember->script == NULL) { GNWSystemError("\n Error!: partyMemberPrepLoad: Out of memory!"); exit(1); } memcpy(partyMember->script, script, sizeof(*script)); if (script->localVarsCount != 0 && script->localVarsOffset != -1) { partyMember->vars = (int*)mem_malloc(sizeof(*partyMember->vars) * script->localVarsCount); if (partyMember->vars == NULL) { GNWSystemError("\n Error!: partyMemberPrepLoad: Out of memory!"); exit(1); } if (map_local_vars != NULL) { memcpy(partyMember->vars, map_local_vars + script->localVarsOffset, sizeof(int) * script->localVarsCount); } else { debug_printf("\nWarning: partyMemberPrepLoadInstance: No map_local_vars found, but script references them!"); memset(partyMember->vars, 0, sizeof(int) * script->localVarsCount); } } Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); partyMemberItemSave(inventoryItem->item); } script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); scr_remove(script->sid); if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { dude_stand(obj, obj->rotation, -1); } return 0; } // 0x4949C4 int partyMemberRecoverLoad() { if (partyStatePrepped != 1) { debug_printf("\npartyMemberRecoverLoad DENIED"); return -1; } debug_printf("\n"); for (int index = 0; index < partyMemberCount; index++) { if (partyMemberRecoverLoadInstance(&(partyMemberList[index])) != 0) { return -1; } debug_printf("[Party Member %d]: %s\n", index, critter_name(partyMemberList[index].object)); } PartyMember* node = itemSaveListHead; while (node != NULL) { itemSaveListHead = node->next; partyMemberItemRecover(node); mem_free(node); node = itemSaveListHead; } partyStatePrepped = 0; if (!isLoadingGame()) { partyFixMultipleMembers(); } return 0; } // 0x494A88 static int partyMemberRecoverLoadInstance(PartyMember* partyMember) { if (partyMember->script == NULL) { GNWSystemError("\n Error!: partyMemberRecoverLoadInstance: No script!"); return 0; } int scriptType = SCRIPT_TYPE_CRITTER; if (PID_TYPE(partyMember->object->pid) != OBJ_TYPE_CRITTER) { scriptType = SCRIPT_TYPE_ITEM; } int v1 = -1; if (scr_new(&v1, scriptType) == -1) { GNWSystemError("\n Error!: partyMemberRecoverLoad: Can't create script!"); exit(1); } Script* script; if (scr_ptr(v1, &script) == -1) { GNWSystemError("\n Error!: partyMemberRecoverLoad: Can't find script!"); exit(1); } memcpy(script, partyMember->script, sizeof(*script)); int sid = (scriptType << 24) | ((partyMember->object->pid & 0xFFFFFF) + 18000); partyMember->object->sid = sid; script->sid = sid; script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04); mem_free(partyMember->script); partyMember->script = NULL; script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); if (partyMember->vars != NULL) { script->localVarsOffset = map_malloc_local_var(script->localVarsCount); memcpy(map_local_vars + script->localVarsOffset, partyMember->vars, sizeof(int) * script->localVarsCount); } return 0; } // 0x494BBC int partyMemberLoad(File* stream) { int* partyMemberObjectIds = (int*)mem_malloc(sizeof(*partyMemberObjectIds) * (partyMemberMaxCount + 20)); if (partyMemberObjectIds == NULL) { return -1; } // FIXME: partyMemberObjectIds is never free'd in this function, obviously memory leak. if (db_freadInt(stream, &partyMemberCount) == -1) return -1; if (db_freadInt(stream, &partyMemberItemCount) == -1) return -1; partyMemberList->object = obj_dude; if (partyMemberCount != 0) { for (int index = 1; index < partyMemberCount; index++) { if (db_freadInt(stream, &(partyMemberObjectIds[index])) == -1) return -1; } for (int index = 1; index < partyMemberCount; index++) { int objectId = partyMemberObjectIds[index]; Object* object = obj_find_first(); while (object != NULL) { if (object->id == objectId) { break; } object = obj_find_next(); } if (object != NULL) { partyMemberList[index].object = object; } else { debug_printf("Couldn't find party member on map...trying to load anyway.\n"); if (index + 1 >= partyMemberCount) { partyMemberObjectIds[index] = 0; } else { memcpy(&(partyMemberObjectIds[index]), &(partyMemberObjectIds[index + 1]), sizeof(*partyMemberObjectIds) * (partyMemberCount - (index + 1))); } index--; partyMemberCount--; } } if (partyMemberUnPrepSave() == -1) { return -1; } } partyFixMultipleMembers(); for (int index = 1; index < partyMemberMaxCount; index++) { PartyMemberLevelUpInfo* levelUpInfo = &(partyMemberLevelUpInfoList[index]); if (db_freadInt(stream, &(levelUpInfo->field_0)) == -1) return -1; if (db_freadInt(stream, &(levelUpInfo->field_4)) == -1) return -1; if (db_freadInt(stream, &(levelUpInfo->field_8)) == -1) return -1; } return 0; } // 0x494D7C void partyMemberClear() { if (partyStatePrepped) { partyMemberUnPrepSave(); } for (int index = partyMemberCount; index > 1; index--) { partyMemberRemove(partyMemberList[1].object); } partyMemberCount = 1; scr_remove_all(); partyMemberClearItemList(); partyStatePrepped = 0; } // 0x494DD0 int partyMemberSyncPosition() { int clockwiseRotation = (obj_dude->rotation + 2) % ROTATION_COUNT; int counterClockwiseRotation = (obj_dude->rotation + 4) % ROTATION_COUNT; int n = 0; int distance = 2; for (int index = 1; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); Object* partyMemberObj = partyMember->object; if ((partyMemberObj->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(partyMemberObj->pid) == OBJ_TYPE_CRITTER) { int rotation; if ((n % 2) != 0) { rotation = clockwiseRotation; } else { rotation = counterClockwiseRotation; } int tile = tile_num_in_direction(obj_dude->tile, rotation, distance / 2); objPMAttemptPlacement(partyMemberObj, tile, obj_dude->elevation); distance++; n++; } } return 0; } // Heals party members according to their healing rate. // // 0x494EB8 int partyMemberRestingHeal(int a1) { int v1 = a1 / 3; if (v1 == 0) { return 0; } for (int index = 0; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); if (PID_TYPE(partyMember->object->pid) == OBJ_TYPE_CRITTER) { int healingRate = critterGetStat(partyMember->object, STAT_HEALING_RATE); critter_adjust_hits(partyMember->object, v1 * healingRate); } } return 1; } // 0x494F24 Object* partyMemberFindObjFromPid(int pid) { for (int index = 0; index < partyMemberCount; index++) { Object* object = partyMemberList[index].object; if (object->pid == pid) { return object; } } return NULL; } // 0x494F64 bool isPotentialPartyMember(Object* object) { for (int index = 0; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); if (partyMember->object->pid == partyMemberPidList[index]) { return true; } } return false; } // Returns `true` if specified object is a party member. // // 0x494FC4 bool isPartyMember(Object* object) { if (object == NULL) { return false; } if (object->id < 18000) { return false; } bool isPartyMember = false; for (int index = 0; index < partyMemberCount; index++) { if (partyMemberList[index].object == object) { isPartyMember = true; break; } } return isPartyMember; } // Returns number of active critters in the party. // // 0x495010 int getPartyMemberCount() { int count = partyMemberCount; for (int index = 1; index < partyMemberCount; index++) { Object* object = partyMemberList[index].object; if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER || critter_is_dead(object) || (object->flags & OBJECT_HIDDEN) != 0) { count--; } } return count; } // 0x495070 static int partyMemberNewObjID() { // 0x519DC0 static int curID = 20000; Object* object; do { curID++; object = obj_find_first(); while (object != NULL) { if (object->id == curID) { break; } Inventory* inventory = &(object->data.inventory); int index; for (index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); Object* item = inventoryItem->item; if (item->id == curID) { break; } if (partyMemberNewObjIDRecurseFind(item, curID)) { break; } } if (index < inventory->length) { break; } object = obj_find_next(); } } while (object != NULL); curID++; return curID; } // 0x4950F4 static int partyMemberNewObjIDRecurseFind(Object* obj, int objectId) { Inventory* inventory = &(obj->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); if (inventoryItem->item->id == objectId) { return 1; } if (partyMemberNewObjIDRecurseFind(inventoryItem->item, objectId)) { return 1; } } return 0; } // 0x495140 int partyMemberPrepItemSaveAll() { for (int partyMemberIndex = 0; partyMemberIndex < partyMemberCount; partyMemberIndex++) { PartyMember* partyMember = &(partyMemberList[partyMemberIndex]); Inventory* inventory = &(partyMember->object->data.inventory); for (int inventoryItemIndex = 0; inventoryItemIndex < inventory->length; inventoryItemIndex++) { InventoryItem* inventoryItem = &(inventory->items[inventoryItemIndex]); partyMemberPrepItemSave(inventoryItem->item); } } return 0; } // 0x495198 static int partyMemberPrepItemSave(Object* object) { if (object->sid != -1) { Script* script; if (scr_ptr(object->sid, &script) == -1) { GNWSystemError("\n Error!: partyMemberPrepItemSaveAll: Can't find script!"); exit(1); } script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); } Inventory* inventory = &(object->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); partyMemberPrepItemSave(inventoryItem->item); } return 0; } // 0x495234 static int partyMemberItemSave(Object* object) { if (object->sid != -1) { Script* script; if (scr_ptr(object->sid, &script) == -1) { GNWSystemError("\n Error!: partyMemberItemSave: Can't find script!"); exit(1); } if (object->id < 20000) { script->field_1C = partyMemberNewObjID(); object->id = script->field_1C; } PartyMember* node = (PartyMember*)mem_malloc(sizeof(*node)); if (node == NULL) { GNWSystemError("\n Error!: partyMemberItemSave: Out of memory!"); exit(1); } node->object = object; node->script = (Script*)mem_malloc(sizeof(*script)); if (node->script == NULL) { GNWSystemError("\n Error!: partyMemberItemSave: Out of memory!"); exit(1); } memcpy(node->script, script, sizeof(*script)); if (script->localVarsCount != 0 && script->localVarsOffset != -1) { node->vars = (int*)mem_malloc(sizeof(*node->vars) * script->localVarsCount); if (node->vars == NULL) { GNWSystemError("\n Error!: partyMemberItemSave: Out of memory!"); exit(1); } memcpy(node->vars, map_local_vars + script->localVarsOffset, sizeof(int) * script->localVarsCount); } else { node->vars = NULL; } PartyMember* temp = itemSaveListHead; itemSaveListHead = node; node->next = temp; } Inventory* inventory = &(object->data.inventory); for (int index = 0; index < inventory->length; index++) { InventoryItem* inventoryItem = &(inventory->items[index]); partyMemberItemSave(inventoryItem->item); } return 0; } // 0x495388 static int partyMemberItemRecover(PartyMember* partyMember) { int sid = -1; if (scr_new(&sid, SCRIPT_TYPE_ITEM) == -1) { GNWSystemError("\n Error!: partyMemberItemRecover: Can't create script!"); exit(1); } Script* script; if (scr_ptr(sid, &script) == -1) { GNWSystemError("\n Error!: partyMemberItemRecover: Can't find script!"); exit(1); } memcpy(script, partyMember->script, sizeof(*script)); partyMember->object->sid = partyMemberItemCount | (SCRIPT_TYPE_ITEM << 24); script->sid = partyMemberItemCount | (SCRIPT_TYPE_ITEM << 24); script->program = NULL; script->flags &= ~(SCRIPT_FLAG_0x01 | SCRIPT_FLAG_0x04 | SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); partyMemberItemCount++; mem_free(partyMember->script); partyMember->script = NULL; if (partyMember->vars != NULL) { script->localVarsOffset = map_malloc_local_var(script->localVarsCount); memcpy(map_local_vars + script->localVarsOffset, partyMember->vars, sizeof(int) * script->localVarsCount); } return 0; } // 0x4954C4 static int partyMemberClearItemList() { while (itemSaveListHead != NULL) { PartyMember* node = itemSaveListHead; itemSaveListHead = itemSaveListHead->next; if (node->script != NULL) { mem_free(node->script); } if (node->vars != NULL) { mem_free(node->vars); } mem_free(node); } partyMemberItemCount = 20000; return 0; } // Returns best skill of the specified party member. // // 0x495520 int partyMemberSkill(Object* object) { int bestSkill = SKILL_SMALL_GUNS; if (object == NULL) { return bestSkill; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return bestSkill; } int bestValue = 0; for (int skill = 0; skill < SKILL_COUNT; skill++) { int value = skill_level(object, skill); if (value > bestValue) { bestSkill = skill; bestValue = value; } } return bestSkill; } // Returns party member with highest skill level. // // 0x495560 Object* partyMemberWithHighestSkill(int skill) { int bestValue = 0; Object* bestPartyMember = NULL; for (int index = 0; index < partyMemberCount; index++) { Object* object = partyMemberList[index].object; if ((object->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { int value = skill_level(object, skill); if (value > bestValue) { bestValue = value; bestPartyMember = object; } } } return bestPartyMember; } // Returns highest skill level in party. // // 0x4955C8 int partyMemberHighestSkillLevel(int skill) { int bestValue = 0; for (int index = 0; index < partyMemberCount; index++) { Object* object = partyMemberList[index].object; if ((object->flags & OBJECT_HIDDEN) == 0 && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { int value = skill_level(object, skill); if (value > bestValue) { bestValue = value; } } } return bestValue; } // 0x495620 static int partyFixMultipleMembers() { debug_printf("\n\n\n[Party Members]:"); int critterCount = 0; for (Object* obj = obj_find_first(); obj != NULL; obj = obj_find_next()) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { critterCount++; } bool isPartyMember = false; for (int index = 1; index < partyMemberMaxCount; index++) { if (obj->pid == partyMemberPidList[index]) { isPartyMember = true; break; } } if (!isPartyMember) { continue; } debug_printf("\n PM: %s", critter_name(obj)); bool v19 = false; if (obj->sid == -1) { v19 = true; } else { Object* v7 = NULL; for (int i = 0; i < partyMemberCount; i++) { if (obj->pid == partyMemberList[i].object->pid) { v7 = partyMemberList[i].object; break; } } if (v7 != NULL && obj != v7) { if (v7->sid == obj->sid) { obj->sid = -1; } v19 = true; } } if (!v19) { continue; } Object* v10 = NULL; for (int i = 0; i < partyMemberCount; i++) { if (obj->pid == partyMemberList[i].object->pid) { v10 = partyMemberList[i].object; } } // TODO: Probably wrong. if (obj == v10) { debug_printf("\nError: Attempting to destroy evil critter doppleganger FAILED!"); continue; } debug_printf("\nDestroying evil critter doppleganger!"); if (obj->sid != -1) { scr_remove(obj->sid); obj->sid = -1; } else { if (queue_remove_this(obj, EVENT_TYPE_SCRIPT) == -1) { debug_printf("\nERROR Removing Timed Events on FIX remove!!\n"); } } obj_erase_object(obj, NULL); } for (int index = 0; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); Script* script; if (scr_ptr(partyMember->object->sid, &script) != -1) { script->owner = partyMember->object; } else { debug_printf("\nError: Failed to fix party member critter scripts!"); } } debug_printf("\nTotal Critter Count: %d\n\n", critterCount); return 0; } // 0x495870 void partyMemberSaveProtos() { for (int index = 1; index < partyMemberMaxCount; index++) { int pid = partyMemberPidList[index]; if (pid != -1) { proto_save_pid(pid); } } } // 0x4958B0 bool partyMemberHasAIDisposition(Object* critter, int disposition) { if (critter == NULL) { return false; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { return false; } if (disposition == -1 || disposition > 5) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(critter, &aiOptions) == -1) { return false; } return aiOptions->disposition[disposition + 1]; } // 0x495920 bool partyMemberHasAIBurstValue(Object* object, int areaAttackMode) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return false; } if (areaAttackMode >= AREA_ATTACK_MODE_COUNT) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(object, &aiOptions) == -1) { return false; } return aiOptions->area_attack_mode[areaAttackMode]; } // 0x495980 bool partyMemberHasAIRunAwayValue(Object* object, int runAwayMode) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return false; } if (runAwayMode >= RUN_AWAY_MODE_COUNT) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(object, &aiOptions) == -1) { return false; } return aiOptions->run_away_mode[runAwayMode + 1]; } // 0x4959E0 bool partyMemberHasAIWeaponPrefValue(Object* object, int bestWeapon) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return false; } if (bestWeapon >= BEST_WEAPON_COUNT) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(object, &aiOptions) == -1) { return false; } return aiOptions->best_weapon[bestWeapon]; } // 0x495A40 bool partyMemberHasAIDistancePrefValue(Object* object, int distanceMode) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return false; } if (distanceMode >= DISTANCE_COUNT) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(object, &aiOptions) == -1) { return false; } return aiOptions->distance_mode[distanceMode]; } // 0x495AA0 bool partyMemberHasAIAttackWhoValue(Object* object, int attackWho) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return false; } if (attackWho >= ATTACK_WHO_COUNT) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(object, &aiOptions) == -1) { return false; } return aiOptions->attack_who[attackWho]; } // 0x495B00 bool partyMemberHasAIChemUseValue(Object* object, int chemUse) { if (object == NULL) { return false; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { return false; } if (chemUse >= CHEM_USE_COUNT) { return false; } PartyMemberAI* aiOptions; if (partyMemberGetAIOptions(object, &aiOptions) == -1) { return false; } return aiOptions->chem_use[chemUse]; } // partyMemberIncLevels // 0x495B60 int partyMemberIncLevels() { int i; PartyMember* partyMember; Object* obj; PartyMemberAI* aiOptions; const char* name; int j; int v0; PartyMemberLevelUpInfo* levelUpInfo; int v24; char* text; MessageListItem msg; char str[260]; Rect v19; v0 = -1; for (i = 1; i < partyMemberCount; i++) { partyMember = &(partyMemberList[i]); obj = partyMember->object; if (partyMemberGetAIOptions(obj, &aiOptions) == -1) { break; } if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { continue; } name = critter_name(obj); debug_printf("\npartyMemberIncLevels: %s", name); if (aiOptions->level_up_every == 0) { continue; } for (j = 1; j < partyMemberMaxCount; j++) { if (partyMemberPidList[j] == obj->pid) { v0 = j; } } if (v0 == -1) { continue; } if (stat_pc_get(PC_STAT_LEVEL) < aiOptions->level_minimum) { continue; } levelUpInfo = &(partyMemberLevelUpInfoList[v0]); if (levelUpInfo->field_0 >= aiOptions->level_pids_num) { continue; } levelUpInfo->field_4++; v24 = levelUpInfo->field_4 % aiOptions->level_pids_num; debug_printf("pm: levelMod: %d, Lvl: %d, Early: %d, Every: %d", v24, levelUpInfo->field_4, levelUpInfo->field_8, aiOptions->level_up_every); if (v24 != 0 || levelUpInfo->field_8 == 0) { if (levelUpInfo->field_8 == 0) { if (v24 == 0 || roll_random(0, 100) <= 100 * v24 / aiOptions->level_up_every) { levelUpInfo->field_0++; if (v24 != 0) { levelUpInfo->field_8 = 1; } if (partyMemberCopyLevelInfo(obj, aiOptions->level_pids[levelUpInfo->field_0]) == -1) { return -1; } name = critter_name(obj); // %s has gained in some abilities. text = getmsg(&misc_message_file, &msg, 9000); sprintf(str, text, name); display_print(str); debug_printf(str); // Individual message msg.num = 9000 + 10 * v0 + levelUpInfo->field_0 - 1; if (message_search(&misc_message_file, &msg)) { name = critter_name(obj); sprintf(str, msg.text, name); text_object_create(obj, str, 101, colorTable[0x7FFF], colorTable[0], &v19); tile_refresh_rect(&v19, obj->elevation); } } } } else { levelUpInfo->field_8 = 0; } } return 0; } // 0x495EA8 static int partyMemberCopyLevelInfo(Object* critter, int a2) { if (critter == NULL) { return -1; } if (a2 == -1) { return -1; } Proto* proto1; if (proto_ptr(critter->pid, &proto1) == -1) { return -1; } Proto* proto2; if (proto_ptr(a2, &proto2) == -1) { return -1; } Object* item2 = inven_right_hand(critter); invenUnwieldFunc(critter, 1, 0); Object* armor = inven_worn(critter); adjust_ac(critter, armor, NULL); item_remove_mult(critter, armor, 1); int maxHp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS); critter_adjust_hits(critter, maxHp); for (int stat = 0; stat < SPECIAL_STAT_COUNT; stat++) { proto1->critter.data.baseStats[stat] = proto2->critter.data.baseStats[stat]; } for (int stat = 0; stat < SPECIAL_STAT_COUNT; stat++) { proto1->critter.data.bonusStats[stat] = proto2->critter.data.bonusStats[stat]; } for (int skill = 0; skill < SKILL_COUNT; skill++) { proto1->critter.data.skills[skill] = proto2->critter.data.skills[skill]; } critter->data.critter.hp = critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS); if (armor != NULL) { item_add_force(critter, armor, 1); inven_wield(critter, armor, 0); } if (item2 != NULL) { invenWieldFunc(critter, item2, 0, false); } return 0; } // Returns `true` if any party member that can be healed thru the rest is // wounded. // // This function is used to determine if any party member needs healing thru // the "Rest until party healed", therefore it excludes robots in the party // (they cannot be healed by resting) and dude (he/she has it's own "Rest // until healed" option). // // 0x496058 bool partyMemberNeedsHealing() { for (int index = 1; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); Object* object = partyMember->object; if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue; if (critter_is_dead(object)) continue; if ((object->flags & OBJECT_HIDDEN) != 0) continue; if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue; int currentHp = critter_get_hits(object); int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS); if (currentHp < maximumHp) { return true; } } return false; } // Returns maximum amount of damage of any party member that can be healed thru // the rest. // // 0x4960DC int partyMemberMaxHealingNeeded() { int maxWound = 0; for (int index = 1; index < partyMemberCount; index++) { PartyMember* partyMember = &(partyMemberList[index]); Object* object = partyMember->object; if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) continue; if (critter_is_dead(object)) continue; if ((object->flags & OBJECT_HIDDEN) != 0) continue; if (critterGetKillType(object) == KILL_TYPE_ROBOT) continue; int currentHp = critter_get_hits(object); int maximumHp = critterGetStat(object, STAT_MAXIMUM_HIT_POINTS); int wound = maximumHp - currentHp; if (wound > 0) { if (wound > maxWound) { maxWound = wound; } } } return maxWound; } ================================================ FILE: src/game/party.h ================================================ #ifndef FALLOUT_GAME_PARTY_H_ #define FALLOUT_GAME_PARTY_H_ #include #include "plib/db/db.h" #include "game/object_types.h" extern int partyMemberMaxCount; extern int* partyMemberPidList; int partyMember_init(); void partyMember_reset(); void partyMember_exit(); int partyMemberAdd(Object* object); int partyMemberRemove(Object* object); int partyMemberPrepSave(); int partyMemberUnPrepSave(); int partyMemberSave(File* stream); int partyMemberPrepLoad(); int partyMemberRecoverLoad(); int partyMemberLoad(File* stream); void partyMemberClear(); int partyMemberSyncPosition(); int partyMemberRestingHeal(int a1); Object* partyMemberFindObjFromPid(int a1); bool isPotentialPartyMember(Object* object); bool isPartyMember(Object* object); int getPartyMemberCount(); int partyMemberPrepItemSaveAll(); int partyMemberSkill(Object* object); Object* partyMemberWithHighestSkill(int skill); int partyMemberHighestSkillLevel(int skill); void partyMemberSaveProtos(); bool partyMemberHasAIDisposition(Object* object, int disposition); bool partyMemberHasAIBurstValue(Object* object, int areaAttackMode); bool partyMemberHasAIRunAwayValue(Object* object, int runAwayMode); bool partyMemberHasAIWeaponPrefValue(Object* object, int bestWeapon); bool partyMemberHasAIDistancePrefValue(Object* object, int distanceMode); bool partyMemberHasAIAttackWhoValue(Object* object, int attackWho); bool partyMemberHasAIChemUseValue(Object* object, int chemUse); int partyMemberIncLevels(); bool partyMemberNeedsHealing(); int partyMemberMaxHealingNeeded(); #endif /* FALLOUT_GAME_PARTY_H_ */ ================================================ FILE: src/game/perk.c ================================================ #include "game/perk.h" #include #include "plib/gnw/debug.h" #include "game/game.h" #include "game/gconfig.h" #include "game/message.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/party.h" #include "game/skill.h" #include "game/stat.h" typedef struct PerkDescription { char* name; char* description; int frmId; int maxRank; int minLevel; int stat; int statModifier; int param1; int value1; int field_24; int param2; int value2; int stats[PRIMARY_STAT_COUNT]; } PerkDescription; static bool perk_can_add(Object* critter, int perk); static void perk_defaults(); // 0x519DCC static PerkDescription perk_data[PERK_COUNT] = { { NULL, NULL, 72, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 5, 0, 0, 0, 0, 0 }, { NULL, NULL, 73, 1, 15, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 }, { NULL, NULL, 74, 3, 3, 11, 2, -1, 0, 0, -1, 0, 6, 0, 0, 0, 0, 6, 0 }, { NULL, NULL, 75, 2, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 }, { NULL, NULL, 76, 2, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 6 }, { NULL, NULL, 77, 1, 15, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 6, 7, 0 }, { NULL, NULL, 78, 3, 3, 13, 2, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 }, { NULL, NULL, 79, 3, 3, 14, 2, -1, 0, 0, -1, 0, 0, 0, 6, 0, 0, 0, 0 }, { NULL, NULL, 80, 3, 6, 15, 5, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 6 }, { NULL, NULL, 81, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 }, { NULL, NULL, 82, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 6, 0, 0, 0 }, { NULL, NULL, 83, 2, 6, 31, 15, -1, 0, 0, -1, 0, 0, 0, 6, 0, 4, 0, 0 }, { NULL, NULL, 84, 3, 3, 24, 10, -1, 0, 0, -1, 0, 0, 0, 6, 0, 0, 0, 6 }, { NULL, NULL, 85, 3, 3, 12, 50, -1, 0, 0, -1, 0, 6, 0, 6, 0, 0, 0, 0 }, { NULL, NULL, 86, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 6, 0, 0 }, { NULL, NULL, 87, 1, 6, -1, 0, 8, 50, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 }, { NULL, NULL, 88, 1, 3, -1, 0, 17, 40, 0, -1, 0, 0, 0, 6, 0, 6, 0, 0 }, { NULL, NULL, 89, 1, 12, -1, 0, 15, 75, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0 }, { NULL, NULL, 90, 3, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0 }, { NULL, NULL, 91, 2, 3, -1, 0, 6, 40, 0, -1, 0, 0, 7, 0, 0, 5, 6, 0 }, { NULL, NULL, 92, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 8 }, { NULL, NULL, 93, 1, 9, 16, 20, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 4, 6 }, { NULL, NULL, 94, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 5, 0, 0 }, { NULL, NULL, 95, 1, 24, -1, 0, 3, 80, 0, -1, 0, 8, 0, 0, 0, 0, 8, 0 }, { NULL, NULL, 96, 1, 24, -1, 0, 0, 80, 0, -1, 0, 0, 8, 0, 0, 0, 8, 0 }, { NULL, NULL, 97, 1, 18, -1, 0, 8, 80, 2, 3, 80, 0, 0, 0, 0, 0, 10, 0 }, { NULL, NULL, 98, 2, 12, 8, 1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 }, { NULL, NULL, 99, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 100, 2, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 4, 0, 0, 0, 0 }, { NULL, NULL, 101, 1, 9, 9, 5, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 6, 0 }, { NULL, NULL, 102, 2, 6, 32, 25, -1, 0, 0, -1, 0, 0, 0, 3, 0, 0, 0, 0 }, { NULL, NULL, 103, 1, 12, -1, 0, 13, 40, 1, 12, 40, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 104, 1, 12, -1, 0, 6, 40, 1, 7, 40, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 105, 1, 12, -1, 0, 10, 50, 2, 9, 50, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 106, 1, 9, -1, 0, 14, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 107, 3, 6, -1, 0, -1, 0, 0, -1, 0, -9, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 108, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 4, 0, 0, 0, 0, 0 }, { NULL, NULL, 109, 1, 15, -1, 0, 10, 80, 0, -1, 0, 0, 0, 0, 0, 0, 8, 0 }, { NULL, NULL, 110, 1, 6, -1, 0, 8, 60, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 111, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 10, 0, 0, 0 }, { NULL, NULL, 112, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 8 }, { NULL, NULL, 113, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 114, 1, 310, -1, 0, -1, 0, 0, -1, 0, 0, 0, 5, 0, 0, 0, 0 }, { NULL, NULL, 115, 2, 6, -1, 0, 17, 40, 0, -1, 0, 0, 0, 6, 0, 0, 0, 0 }, { NULL, NULL, 116, 1, 310, -1, 0, 17, 25, 0, -1, 0, 0, 0, 0, 0, 5, 0, 0 }, { NULL, NULL, 117, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 7, 0, 0, 0, 0, 0 }, { NULL, NULL, 118, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 4 }, { NULL, NULL, 119, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 }, { NULL, NULL, 120, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 }, { NULL, NULL, 121, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 4, 0, 0 }, { NULL, NULL, 122, 3, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 4, 0, 0 }, { NULL, NULL, 123, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 124, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 125, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 126, -1, 1, -1, 0, -1, 0, 0, -1, 0, -2, 0, -2, 0, 0, -3, 0 }, { NULL, NULL, 127, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -3, -2, 0 }, { NULL, NULL, 128, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -2, 0, 0 }, { NULL, NULL, 129, -1, 1, 31, -20, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 130, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 131, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 132, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 133, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 134, -1, 1, 31, 30, -1, 0, 0, -1, 0, 3, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 135, -1, 1, 31, 20, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 136, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 137, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 138, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 139, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 140, -1, 1, 31, 60, -1, 0, 0, -1, 0, 4, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 141, -1, 1, 31, 75, -1, 0, 0, -1, 0, 4, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 136, -1, 1, 8, -1, -1, 0, 0, -1, 0, -1, -1, 0, 0, 0, 0, 0 }, { NULL, NULL, 149, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, -2, 0, 0, -1, 0, -1 }, { NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 2, 0, 0, 0 }, { NULL, NULL, 158, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 157, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 157, -1, 1, 3, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 168, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 168, -1, 1, 3, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 172, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 155, 1, 6, -1, 0, -1, 0, 0, -1, 0, -10, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 156, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 6, 0, 0, 0, 0, 0 }, { NULL, NULL, 122, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 6, 0, 0 }, { NULL, NULL, 39, 1, 9, -1, 0, 11, 75, 0, -1, 0, 0, 0, 0, 0, 0, 4, 0 }, { NULL, NULL, 44, 1, 6, -1, 0, 16, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 0, 1, 12, -1, 0, -1, 0, 0, -1, 0, -10, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 1, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, -10, 0, 0, 0, 0, 0 }, { NULL, NULL, 2, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, -10, 0, 0, 0, 0 }, { NULL, NULL, 3, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, -10, 0, 0, 0 }, { NULL, NULL, 4, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, -10, 0, 0 }, { NULL, NULL, 5, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, -10, 0 }, { NULL, NULL, 6, 1, 12, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, -10 }, { NULL, NULL, 160, 1, 6, -1, 0, 10, 50, 2, 0x4000000, 50, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 161, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 159, 1, 12, -1, 0, 3, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 163, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 5, 0, 0, 5, 0 }, { NULL, NULL, 162, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 6, 0, 0, 0 }, { NULL, NULL, 164, 1, 9, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 5 }, { NULL, NULL, 165, 1, 12, -1, 0, 7, 60, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 166, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, -10, 0, 0, 0 }, { NULL, NULL, 43, 1, 6, -1, 0, 15, 50, 2, 14, 50, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 167, 1, 6, 12, 50, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 169, 1, 9, -1, 0, 1, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 170, 1, 6, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 5, 0 }, { NULL, NULL, 121, 1, 6, -1, 0, 15, 50, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 171, 1, 3, -1, 0, -1, 0, 0, -1, 0, 6, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 38, 1, 3, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 173, 1, 12, -1, 0, -1, 0, 0, -1, 0, -7, 0, 0, 0, 0, 5, 0 }, { NULL, NULL, 104, -1, 1, -1, 0, 7, 75, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 142, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 142, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 52, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 52, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 104, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 104, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 35, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 35, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 154, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, { NULL, NULL, 64, -1, 1, -1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0 }, }; // An array of perk ranks for each party member. // // 0x51C120 static PerkRankData* perkLevelDataList = NULL; // Amount of experience points granted when player selected "Here and now" // perk. // // 0x51C124 static int hereAndNowExps = 0; // perk.msg // // 0x6642D4 static MessageList perk_message_file; // 0x4965A0 int perk_init() { perkLevelDataList = (PerkRankData*)mem_malloc(sizeof(*perkLevelDataList) * partyMemberMaxCount); if (perkLevelDataList == NULL) { return -1; } perk_defaults(); if (!message_init(&perk_message_file)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "perk.msg"); if (!message_load(&perk_message_file, path)) { return -1; } for (int perk = 0; perk < PERK_COUNT; perk++) { MessageListItem messageListItem; messageListItem.num = 101 + perk; if (message_search(&perk_message_file, &messageListItem)) { perk_data[perk].name = messageListItem.text; } messageListItem.num = 1101 + perk; if (message_search(&perk_message_file, &messageListItem)) { perk_data[perk].description = messageListItem.text; } } return 0; } // 0x4966B0 void perk_reset() { perk_defaults(); } // 0x4966B8 void perk_exit() { message_exit(&perk_message_file); if (perkLevelDataList != NULL) { mem_free(perkLevelDataList); perkLevelDataList = NULL; } } // 0x4966E4 int perk_load(File* stream) { for (int index = 0; index < partyMemberMaxCount; index++) { PerkRankData* ranksData = &(perkLevelDataList[index]); for (int perk = 0; perk < PERK_COUNT; perk++) { if (db_freadInt(stream, &(ranksData->ranks[perk])) == -1) { return -1; } } } return 0; } // 0x496738 int perk_save(File* stream) { for (int index = 0; index < partyMemberMaxCount; index++) { PerkRankData* ranksData = &(perkLevelDataList[index]); for (int perk = 0; perk < PERK_COUNT; perk++) { if (db_fwriteInt(stream, ranksData->ranks[perk]) == -1) { return -1; } } } return 0; } // perkGetLevelData // 0x49678C PerkRankData* perkGetLevelData(Object* critter) { if (critter == obj_dude) { return perkLevelDataList; } for (int index = 1; index < partyMemberMaxCount; index++) { if (critter->pid == partyMemberPidList[index]) { return perkLevelDataList + index; } } debug_printf("\nError: perkGetLevelData: Can't find party member match!"); return perkLevelDataList; } // 0x49680C static bool perk_can_add(Object* critter, int perk) { if (!perkIsValid(perk)) { return false; } PerkDescription* perkDescription = &(perk_data[perk]); if (perkDescription->maxRank == -1) { return false; } PerkRankData* ranksData = perkGetLevelData(critter); if (ranksData->ranks[perk] >= perkDescription->maxRank) { return false; } if (critter == obj_dude) { if (stat_pc_get(PC_STAT_LEVEL) < perkDescription->minLevel) { return false; } } bool v1 = true; int param1 = perkDescription->param1; if (param1 != -1) { bool isVariable = false; if ((param1 & 0x4000000) != 0) { isVariable = true; param1 &= ~0x4000000; } int value1 = perkDescription->value1; if (value1 < 0) { if (isVariable) { if (game_get_global_var(param1) >= value1) { v1 = false; } } else { if (skill_level(critter, param1) >= -value1) { v1 = false; } } } else { if (isVariable) { if (game_get_global_var(param1) < value1) { v1 = false; } } else { if (skill_level(critter, param1) < value1) { v1 = false; } } } } if (!v1 || perkDescription->field_24 == 2) { if (perkDescription->field_24 == 0) { return false; } if (!v1 && perkDescription->field_24 == 2) { return false; } int param2 = perkDescription->param2; bool isVariable = false; if (param2 != -1) { if ((param2 & 0x4000000) != 0) { isVariable = true; param2 &= ~0x4000000; } } if (param2 == -1) { return false; } int value2 = perkDescription->value2; if (value2 < 0) { if (isVariable) { if (game_get_global_var(param2) >= value2) { return false; } } else { if (skill_level(critter, param2) >= -value2) { return false; } } } else { if (isVariable) { if (game_get_global_var(param2) < value2) { return false; } } else { if (skill_level(critter, param2) < value2) { return false; } } } } for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { if (perkDescription->stats[stat] < 0) { if (critterGetStat(critter, stat) >= -perkDescription->stats[stat]) { return false; } } else { if (critterGetStat(critter, stat) < perkDescription->stats[stat]) { return false; } } } return true; } // Resets party member perks. // // 0x496A0C static void perk_defaults() { for (int index = 0; index < partyMemberMaxCount; index++) { PerkRankData* ranksData = &(perkLevelDataList[index]); for (int perk = 0; perk < PERK_COUNT; perk++) { ranksData->ranks[perk] = 0; } } } // 0x496A5C int perk_add(Object* critter, int perk) { if (!perkIsValid(perk)) { return -1; } if (!perk_can_add(critter, perk)) { return -1; } PerkRankData* ranksData = perkGetLevelData(critter); ranksData->ranks[perk] += 1; perk_add_effect(critter, perk); return 0; } // perk_add_force // 0x496A9C int perk_add_force(Object* critter, int perk) { if (!perkIsValid(perk)) { return -1; } PerkRankData* ranksData = perkGetLevelData(critter); int value = ranksData->ranks[perk]; int maxRank = perk_data[perk].maxRank; if (maxRank != -1 && value >= maxRank) { return -1; } ranksData->ranks[perk] += 1; perk_add_effect(critter, perk); return 0; } // perk_sub // 0x496AFC int perk_sub(Object* critter, int perk) { if (!perkIsValid(perk)) { return -1; } PerkRankData* ranksData = perkGetLevelData(critter); int value = ranksData->ranks[perk]; if (value < 1) { return -1; } ranksData->ranks[perk] -= 1; perk_remove_effect(critter, perk); return 0; } // Returns perks available to pick. // // 0x496B44 int perk_make_list(Object* critter, int* perks) { int count = 0; for (int perk = 0; perk < PERK_COUNT; perk++) { if (perk_can_add(critter, perk)) { perks[count] = perk; count++; } } return count; } // has_perk // 0x496B78 int perk_level(Object* critter, int perk) { if (!perkIsValid(perk)) { return 0; } PerkRankData* ranksData = perkGetLevelData(critter); return ranksData->ranks[perk]; } // 0x496B90 char* perk_name(int perk) { if (!perkIsValid(perk)) { return NULL; } return perk_data[perk].name; } // 0x496BB4 char* perk_description(int perk) { if (!perkIsValid(perk)) { return NULL; } return perk_data[perk].description; } // 0x496BD8 int perk_skilldex_fid(int perk) { if (!perkIsValid(perk)) { return 0; } return perk_data[perk].frmId; } // perk_add_effect // 0x496BFC void perk_add_effect(Object* critter, int perk) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { debug_printf("\nERROR: perk_add_effect: Was called on non-critter!"); return; } if (!perkIsValid(perk)) { return; } PerkDescription* perkDescription = &(perk_data[perk]); if (perkDescription->stat != -1) { int value = stat_get_bonus(critter, perkDescription->stat); stat_set_bonus(critter, perkDescription->stat, value + perkDescription->statModifier); } if (perk == PERK_HERE_AND_NOW) { PerkRankData* ranksData = perkGetLevelData(critter); ranksData->ranks[PERK_HERE_AND_NOW] -= 1; int level = stat_pc_get(PC_STAT_LEVEL); hereAndNowExps = statPcMinExpForLevel(level + 1) - stat_pc_get(PC_STAT_EXPERIENCE); statPCAddExperienceCheckPMs(hereAndNowExps, false); ranksData->ranks[PERK_HERE_AND_NOW] += 1; } if (perkDescription->maxRank == -1) { for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { int value = stat_get_bonus(critter, stat); stat_set_bonus(critter, stat, value + perkDescription->stats[stat]); } } } // perk_remove_effect // 0x496CE0 void perk_remove_effect(Object* critter, int perk) { if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { debug_printf("\nERROR: perk_remove_effect: Was called on non-critter!"); return; } if (!perkIsValid(perk)) { return; } PerkDescription* perkDescription = &(perk_data[perk]); if (perkDescription->stat != -1) { int value = stat_get_bonus(critter, perkDescription->stat); stat_set_bonus(critter, perkDescription->stat, value - perkDescription->statModifier); } if (perk == PERK_HERE_AND_NOW) { int xp = stat_pc_get(PC_STAT_EXPERIENCE); stat_pc_set(PC_STAT_EXPERIENCE, xp - hereAndNowExps); } if (perkDescription->maxRank == -1) { for (int stat = 0; stat < PRIMARY_STAT_COUNT; stat++) { int value = stat_get_bonus(critter, stat); stat_set_bonus(critter, stat, value - perkDescription->stats[stat]); } } } // Returns modifier to specified skill accounting for perks. // // 0x496DD0 int perk_adjust_skill(Object* critter, int skill) { int modifier = 0; switch (skill) { case SKILL_FIRST_AID: if (perkHasRank(critter, PERK_MEDIC)) { modifier += 10; } if (perkHasRank(critter, PERK_VAULT_CITY_TRAINING)) { modifier += 5; } break; case SKILL_DOCTOR: if (perkHasRank(critter, PERK_MEDIC)) { modifier += 10; } if (perkHasRank(critter, PERK_LIVING_ANATOMY)) { modifier += 10; } if (perkHasRank(critter, PERK_VAULT_CITY_TRAINING)) { modifier += 5; } break; case SKILL_SNEAK: if (perkHasRank(critter, PERK_GHOST)) { int lightIntensity = obj_get_visible_light(obj_dude); if (lightIntensity > 45875) { modifier += 20; } } // FALLTHROUGH case SKILL_LOCKPICK: case SKILL_STEAL: case SKILL_TRAPS: if (perkHasRank(critter, PERK_THIEF)) { modifier += 10; } if (skill == SKILL_LOCKPICK || skill == SKILL_STEAL) { if (perkHasRank(critter, PERK_MASTER_THIEF)) { modifier += 15; } } if (skill == SKILL_STEAL) { if (perkHasRank(critter, PERK_HARMLESS)) { modifier += 20; } } break; case SKILL_SCIENCE: case SKILL_REPAIR: if (perkHasRank(critter, PERK_MR_FIXIT)) { modifier += 10; } break; case SKILL_SPEECH: if (perkHasRank(critter, PERK_SPEAKER)) { modifier += 20; } if (perkHasRank(critter, PERK_EXPERT_EXCREMENT_EXPEDITOR)) { modifier += 5; } // FALLTHROUGH case SKILL_BARTER: if (perkHasRank(critter, PERK_NEGOTIATOR)) { modifier += 10; } if (skill == SKILL_BARTER) { if (perkHasRank(critter, PERK_SALESMAN)) { modifier += 20; } } break; case SKILL_GAMBLING: if (perkHasRank(critter, PERK_GAMBLER)) { modifier += 20; } break; case SKILL_OUTDOORSMAN: if (perkHasRank(critter, PERK_RANGER)) { modifier += 15; } if (perkHasRank(critter, PERK_SURVIVALIST)) { modifier += 25; } break; } return modifier; } ================================================ FILE: src/game/perk.h ================================================ #ifndef FALLOUT_GAME_PERK_H_ #define FALLOUT_GAME_PERK_H_ #include #include "plib/db/db.h" #include "game/object_types.h" #include "game/perk_defs.h" typedef struct PerkRankData { int ranks[PERK_COUNT]; } PerkRankData; int perk_init(); void perk_reset(); void perk_exit(); int perk_load(File* stream); int perk_save(File* stream); PerkRankData* perkGetLevelData(Object* critter); int perk_add(Object* critter, int perk); int perk_add_force(Object* critter, int perk); int perk_sub(Object* critter, int perk); int perk_make_list(Object* critter, int* perks); int perk_level(Object* critter, int perk); char* perk_name(int perk); char* perk_description(int perk); int perk_skilldex_fid(int perk); void perk_add_effect(Object* critter, int perk); void perk_remove_effect(Object* critter, int perk); int perk_adjust_skill(Object* critter, int skill); // Returns true if perk is valid. static inline bool perkIsValid(int perk) { return perk >= 0 && perk < PERK_COUNT; } // Returns true if critter has at least one rank in specified perk. // // NOTE: Most perks have only 1 rank, which means dude either have perk, or // not. // // On the other hand, there are several places in editor, where they made two // consequtive calls to [perk_level], first to check for presence, then get // the actual value for displaying. So a macro could exist, or this very // function, but due to similarity to [perk_level] it could have been // collapsed by compiler. static inline bool perkHasRank(Object* critter, int perk) { return perk_level(critter, perk) != 0; } #endif /* FALLOUT_GAME_PERK_H_ */ ================================================ FILE: src/game/perk_defs.h ================================================ #ifndef PERK_DEFS_H #define PERK_DEFS_H typedef enum Perk { PERK_AWARENESS, PERK_BONUS_HTH_ATTACKS, PERK_BONUS_HTH_DAMAGE, PERK_BONUS_MOVE, PERK_BONUS_RANGED_DAMAGE, PERK_BONUS_RATE_OF_FIRE, PERK_EARLIER_SEQUENCE, PERK_FASTER_HEALING, PERK_MORE_CRITICALS, PERK_NIGHT_VISION, PERK_PRESENCE, PERK_RAD_RESISTANCE, PERK_TOUGHNESS, PERK_STRONG_BACK, PERK_SHARPSHOOTER, PERK_SILENT_RUNNING, PERK_SURVIVALIST, PERK_MASTER_TRADER, PERK_EDUCATED, PERK_HEALER, PERK_FORTUNE_FINDER, PERK_BETTER_CRITICALS, PERK_EMPATHY, PERK_SLAYER, PERK_SNIPER, PERK_SILENT_DEATH, PERK_ACTION_BOY, PERK_MENTAL_BLOCK, PERK_LIFEGIVER, PERK_DODGER, PERK_SNAKEATER, PERK_MR_FIXIT, PERK_MEDIC, PERK_MASTER_THIEF, PERK_SPEAKER, PERK_HEAVE_HO, PERK_FRIENDLY_FOE, PERK_PICKPOCKET, PERK_GHOST, PERK_CULT_OF_PERSONALITY, PERK_SCROUNGER, PERK_EXPLORER, PERK_FLOWER_CHILD, PERK_PATHFINDER, PERK_ANIMAL_FRIEND, PERK_SCOUT, PERK_MYSTERIOUS_STRANGER, PERK_RANGER, PERK_QUICK_POCKETS, PERK_SMOOTH_TALKER, PERK_SWIFT_LEARNER, PERK_TAG, PERK_MUTATE, PERK_NUKA_COLA_ADDICTION, PERK_BUFFOUT_ADDICTION, PERK_MENTATS_ADDICTION, PERK_PSYCHO_ADDICTION, PERK_RADAWAY_ADDICTION, PERK_WEAPON_LONG_RANGE, PERK_WEAPON_ACCURATE, PERK_WEAPON_PENETRATE, PERK_WEAPON_KNOCKBACK, PERK_POWERED_ARMOR, PERK_COMBAT_ARMOR, PERK_WEAPON_SCOPE_RANGE, PERK_WEAPON_FAST_RELOAD, PERK_WEAPON_NIGHT_SIGHT, PERK_WEAPON_FLAMEBOY, PERK_ARMOR_ADVANCED_I, PERK_ARMOR_ADVANCED_II, PERK_JET_ADDICTION, PERK_TRAGIC_ADDICTION, PERK_ARMOR_CHARISMA, PERK_GECKO_SKINNING, PERK_DERMAL_IMPACT_ARMOR, PERK_DERMAL_IMPACT_ASSAULT_ENHANCEMENT, PERK_PHOENIX_ARMOR_IMPLANTS, PERK_PHOENIX_ASSAULT_ENHANCEMENT, PERK_VAULT_CITY_INOCULATIONS, PERK_ADRENALINE_RUSH, PERK_CAUTIOUS_NATURE, PERK_COMPREHENSION, PERK_DEMOLITION_EXPERT, PERK_GAMBLER, PERK_GAIN_STRENGTH, PERK_GAIN_PERCEPTION, PERK_GAIN_ENDURANCE, PERK_GAIN_CHARISMA, PERK_GAIN_INTELLIGENCE, PERK_GAIN_AGILITY, PERK_GAIN_LUCK, PERK_HARMLESS, PERK_HERE_AND_NOW, PERK_HTH_EVADE, PERK_KAMA_SUTRA_MASTER, PERK_KARMA_BEACON, PERK_LIGHT_STEP, PERK_LIVING_ANATOMY, PERK_MAGNETIC_PERSONALITY, PERK_NEGOTIATOR, PERK_PACK_RAT, PERK_PYROMANIAC, PERK_QUICK_RECOVERY, PERK_SALESMAN, PERK_STONEWALL, PERK_THIEF, PERK_WEAPON_HANDLING, PERK_VAULT_CITY_TRAINING, PERK_ALCOHOL_RAISED_HIT_POINTS, PERK_ALCOHOL_RAISED_HIT_POINTS_II, PERK_ALCOHOL_LOWERED_HIT_POINTS, PERK_ALCOHOL_LOWERED_HIT_POINTS_II, PERK_AUTODOC_RAISED_HIT_POINTS, PERK_AUTODOC_RAISED_HIT_POINTS_II, PERK_AUTODOC_LOWERED_HIT_POINTS, PERK_AUTODOC_LOWERED_HIT_POINTS_II, PERK_EXPERT_EXCREMENT_EXPEDITOR, PERK_WEAPON_ENHANCED_KNOCKOUT, PERK_JINXED, PERK_COUNT, } Perk; #endif /* PERK_DEFS_H */ ================================================ FILE: src/game/pipboy.c ================================================ #include "game/pipboy.h" #include #include #include #include "game/automap.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/config.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "game/bmpdlog.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/intface.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #include "game/wordwrap.h" #include "game/worldmap.h" #define PIPBOY_RAND_MAX 32767 #define PIPBOY_WINDOW_WIDTH 640 #define PIPBOY_WINDOW_HEIGHT 480 #define PIPBOY_WINDOW_DAY_X 20 #define PIPBOY_WINDOW_DAY_Y 17 #define PIPBOY_WINDOW_MONTH_X 46 #define PIPBOY_WINDOW_MONTH_Y 18 #define PIPBOY_WINDOW_YEAR_X 83 #define PIPBOY_WINDOW_YEAR_Y 17 #define PIPBOY_WINDOW_TIME_X 155 #define PIPBOY_WINDOW_TIME_Y 17 #define PIPBOY_HOLODISK_LINES_MAX 35 #define PIPBOY_WINDOW_CONTENT_VIEW_X 254 #define PIPBOY_WINDOW_CONTENT_VIEW_Y 46 #define PIPBOY_WINDOW_CONTENT_VIEW_WIDTH 374 #define PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT 410 #define PIPBOY_IDLE_TIMEOUT 120000 #define PIPBOY_BOMB_COUNT 16 #define BACK_BUTTON_INDEX 22 typedef enum Holiday { HOLIDAY_NEW_YEAR, HOLIDAY_VALENTINES_DAY, HOLIDAY_FOOLS_DAY, HOLIDAY_SHIPPING_DAY, HOLIDAY_INDEPENDENCE_DAY, HOLIDAY_HALLOWEEN, HOLIDAY_THANKSGIVING_DAY, HOLIDAY_CRISTMAS, HOLIDAY_COUNT, } Holiday; // Options used to render Pipboy texts. typedef enum PipboyTextOptions { // Specifies that text should be rendered in the center of the Pipboy // monitor. // // This option is mutually exclusive with other alignment options. PIPBOY_TEXT_ALIGNMENT_CENTER = 0x02, // Specifies that text should be rendered in the beginning of the right // column in two-column layout. // // This option is mutually exclusive with other alignment options. PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN = 0x04, // Specifies that text should be rendered in the center of the left column. // // This option is mutually exclusive with other alignment options. PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER = 0x10, // Specifies that text should be rendered in the center of the right // column. // // This option is mutually exclusive with other alignment options. PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER = 0x20, // Specifies that text should rendered with underline. PIPBOY_TEXT_STYLE_UNDERLINE = 0x08, // Specifies that text should rendered with strike-through line. PIPBOY_TEXT_STYLE_STRIKE_THROUGH = 0x40, // Specifies that text should be rendered with no (minimal) indentation. PIPBOY_TEXT_NO_INDENT = 0x80, } PipboyTextOptions; typedef enum PipboyRestDuration { PIPBOY_REST_DURATION_TEN_MINUTES, PIPBOY_REST_DURATION_THIRTY_MINUTES, PIPBOY_REST_DURATION_ONE_HOUR, PIPBOY_REST_DURATION_TWO_HOURS, PIPBOY_REST_DURATION_THREE_HOURS, PIPBOY_REST_DURATION_FOUR_HOURS, PIPBOY_REST_DURATION_FIVE_HOURS, PIPBOY_REST_DURATION_SIX_HOURS, PIPBOY_REST_DURATION_UNTIL_MORNING, PIPBOY_REST_DURATION_UNTIL_NOON, PIPBOY_REST_DURATION_UNTIL_EVENING, PIPBOY_REST_DURATION_UNTIL_MIDNIGHT, PIPBOY_REST_DURATION_UNTIL_HEALED, PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED, PIPBOY_REST_DURATION_COUNT, PIPBOY_REST_DURATION_COUNT_WITHOUT_PARTY = PIPBOY_REST_DURATION_COUNT - 1, } PipboyRestDuration; typedef enum PipboyFrm { PIPBOY_FRM_LITTLE_RED_BUTTON_UP, PIPBOY_FRM_LITTLE_RED_BUTTON_DOWN, PIPBOY_FRM_NUMBERS, PIPBOY_FRM_BACKGROUND, PIPBOY_FRM_NOTE, PIPBOY_FRM_MONTHS, PIPBOY_FRM_NOTE_NUMBERS, PIPBOY_FRM_ALARM_DOWN, PIPBOY_FRM_ALARM_UP, PIPBOY_FRM_LOGO, PIPBOY_FRM_BOMB, PIPBOY_FRM_COUNT, } PipboyFrm; // Provides metadata information on quests. // // Loaded from `data/quest.txt`. typedef struct QuestDescription { int location; int description; int gvar; int displayThreshold; int completedThreshold; } QuestDescription; // Provides metadata information on holodisks. // // Loaded from `data/holodisk.txt`. typedef struct HolodiskDescription { int gvar; int name; int description; } HolodiskDescription; typedef struct HolidayDescription { short month; short day; short textId; } HolidayDescription; typedef struct STRUCT_664350 { char* name; short field_4; short field_6; } STRUCT_664350; typedef struct PipboyBomb { int x; int y; float field_8; float field_C; unsigned char field_10; } PipboyBomb; static int StartPipboy(int intent); static void EndPipboy(); static void pip_num(int value, int digits, int x, int y); static void pip_date(); static void pip_print(const char* text, int a2, int a3); static void pip_back(int a1); static void PipStatus(int a1); static void ListStatLines(int a1); static void ShowHoloDisk(); static int ListHoloDiskTitles(int a1); static int qscmp(const void* a1, const void* a2); static void PipAutomaps(int a1); static int PrintAMelevList(int a1); static int PrintAMList(int a1); static void PipArchives(int a1); static int ListArchive(int a1); static void PipAlarm(int a1); static void DrawAlarmText(int a1); static void DrawAlrmHitPnts(); static void AddHotLines(int a1, int a2, bool add_back_button); static void NixHotLines(); static bool TimedRest(int hours, int minutes, int kind); static bool Check4Health(int a1); static bool AddHealth(); static void ClacTime(int* hours, int* minutes, int wakeUpHour); static int ScreenSaver(); static int quest_init(); static void quest_exit(); static int quest_qsort_compare(const void* a1, const void* a2); static int holodisks_init(); static void holodisks_exit(); // 0x496FC0 static const Rect pip_rect = { PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_Y, PIPBOY_WINDOW_CONTENT_VIEW_X + PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, }; // 0x496FD0 static const int pipgrphs[PIPBOY_FRM_COUNT] = { 8, 9, 82, 127, 128, 129, 130, 131, 132, 133, 226, }; // 0x51C128 static QuestDescription* quests = NULL; // 0x51C12C static int quest_count = 0; // 0x51C130 static HolodiskDescription* holodisks = NULL; // 0x51C134 static int holodisks_count = 0; // Number of rest options available. // // 0x51C138 static int currentAlarmTypeCount = PIPBOY_REST_DURATION_COUNT; // 0x51C13C static bool bk_enable = false; // 0x51C140 static HolidayDescription SpclDate[HOLIDAY_COUNT] = { { 1, 1, 100 }, { 2, 14, 101 }, { 4, 1, 102 }, { 7, 4, 104 }, { 10, 6, 103 }, { 10, 31, 105 }, { 11, 28, 106 }, { 12, 25, 107 }, }; // 0x51C170 PipboyRenderProc* PipFnctn[5] = { PipStatus, PipAutomaps, PipArchives, PipAlarm, PipAlarm, }; // 0x6642E0 static Size ginfo[PIPBOY_FRM_COUNT]; // 0x664338 static MessageListItem pipmesg; // pipboy.msg // // 0x664348 static MessageList pipboy_message_file; // 0x664350 static STRUCT_664350 sortlist[24]; // quests.msg // // 0x664410 static MessageList quest_message_file; // 0x664418 static int statcount; // 0x66441C static unsigned char* scrn_buf; // 0x664420 static unsigned char* pipbmp[PIPBOY_FRM_COUNT]; // 0x66444C static int holocount; // 0x664450 static int mouse_y; // 0x664454 static int mouse_x; // 0x664458 static unsigned int wait_time; // Index of the last page when rendering holodisk content. // // 0x66445C static int holopages; // 0x664460 static int HotLines[23]; // 0x6644BC static int old_mouse_x; // 0x6644C0 static int old_mouse_y; // 0x6644C4 static int pip_win; // 0x6644C8 static CacheEntry* grphkey[PIPBOY_FRM_COUNT]; // 0x6644F4 static int holodisk; // 0x6644F8 static int hot_line_count; // 0x6644FC static int savefont; // 0x664500 static bool proc_bail_flag; // 0x664504 static int amlst_mode; // 0x664508 static int crnt_func; // 0x66450C static int actcnt; // 0x664510 static int hot_line_start; // 0x664514 static int cursor_line; // 0x664518 static int rest_time; // 0x66451C static int amcty_indx; // 0x664520 static int view_page; // 0x664524 static int bottom_line; // 0x664528 static unsigned char hot_back_line; // 0x664529 static unsigned char holo_flag; // 0x66452A static unsigned char stat_flag; // 0x497004 int pipboy(int intent) { if (!wmMapPipboyActive()) { // You aren't wearing the pipboy! const char* text = getmsg(&misc_message_file, &pipmesg, 7000); dialog_out(text, NULL, 0, 192, 135, colorTable[32328], NULL, colorTable[32328], 1); return 0; } intent = StartPipboy(intent); if (intent == -1) { return -1; } mouse_get_position(&old_mouse_x, &old_mouse_y); wait_time = get_time(); while (true) { int keyCode = get_input(); if (intent == PIPBOY_OPEN_INTENT_REST) { keyCode = 504; intent = PIPBOY_OPEN_INTENT_UNSPECIFIED; } mouse_get_position(&mouse_x, &mouse_y); if (keyCode != -1 || mouse_x != old_mouse_x || mouse_y != old_mouse_y) { wait_time = get_time(); old_mouse_x = mouse_x; old_mouse_y = mouse_y; } else { if (get_time() - wait_time > PIPBOY_IDLE_TIMEOUT) { ScreenSaver(); wait_time = get_time(); mouse_get_position(&old_mouse_x, &old_mouse_y); } } if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); break; } if (keyCode == 503 || keyCode == KEY_ESCAPE || keyCode == KEY_RETURN || keyCode == KEY_UPPERCASE_P || keyCode == KEY_LOWERCASE_P || game_user_wants_to_quit != 0) { break; } if (keyCode == KEY_F12) { dump_screen(); } else if (keyCode >= 500 && keyCode <= 504) { crnt_func = keyCode - 500; PipFnctn[crnt_func](1024); } else if (keyCode >= 505 && keyCode <= 527) { PipFnctn[crnt_func](keyCode - 506); } else if (keyCode == 528) { PipFnctn[crnt_func](1025); } else if (keyCode == KEY_PAGE_DOWN) { PipFnctn[crnt_func](1026); } else if (keyCode == KEY_PAGE_UP) { PipFnctn[crnt_func](1027); } if (proc_bail_flag) { break; } } EndPipboy(); return 0; } // 0x497228 static int StartPipboy(int intent) { bk_enable = map_disable_bk_processes(); cycle_disable(); gmouse_3d_off(); disable_box_bar_win(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); currentAlarmTypeCount = PIPBOY_REST_DURATION_COUNT_WITHOUT_PARTY; if (getPartyMemberCount() > 1 && partyMemberNeedsHealing()) { currentAlarmTypeCount = PIPBOY_REST_DURATION_COUNT; } savefont = text_curr(); text_font(101); proc_bail_flag = 0; rest_time = 0; cursor_line = 0; hot_line_count = 0; bottom_line = PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT / text_height() - 1; hot_line_start = 0; hot_back_line = 0; if (holodisks_init() == -1) { return -1; } if (!message_init(&pipboy_message_file)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "pipboy.msg"); if (!(message_load(&pipboy_message_file, path))) { return -1; } int index; for (index = 0; index < PIPBOY_FRM_COUNT; index++) { int fid = art_id(OBJ_TYPE_INTERFACE, pipgrphs[index], 0, 0, 0); pipbmp[index] = art_lock(fid, &(grphkey[index]), &(ginfo[index].width), &(ginfo[index].height)); if (pipbmp[index] == NULL) { break; } } if (index != PIPBOY_FRM_COUNT) { debug_printf("\n** Error loading pipboy graphics! **\n"); while (--index >= 0) { art_ptr_unlock(grphkey[index]); } return -1; } int pipboyWindowX = 0; int pipboyWindowY = 0; pip_win = win_add(pipboyWindowX, pipboyWindowY, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_HEIGHT, colorTable[0], WINDOW_FLAG_0x10); if (pip_win == -1) { debug_printf("\n** Error opening pipboy window! **\n"); for (int index = 0; index < PIPBOY_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } return -1; } scrn_buf = win_get_buf(pip_win); memcpy(scrn_buf, pipbmp[PIPBOY_FRM_BACKGROUND], PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_HEIGHT); pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); pip_date(); int alarmButton = win_register_button(pip_win, 124, 13, ginfo[PIPBOY_FRM_ALARM_UP].width, ginfo[PIPBOY_FRM_ALARM_UP].height, -1, -1, -1, 504, pipbmp[PIPBOY_FRM_ALARM_UP], pipbmp[PIPBOY_FRM_ALARM_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (alarmButton != -1) { win_register_button_sound_func(alarmButton, gsound_med_butt_press, gsound_med_butt_release); } int y = 341; int eventCode = 500; for (int index = 0; index < 5; index += 1) { if (index != 1) { int btn = win_register_button(pip_win, 53, y, ginfo[PIPBOY_FRM_LITTLE_RED_BUTTON_UP].width, ginfo[PIPBOY_FRM_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, eventCode, pipbmp[PIPBOY_FRM_LITTLE_RED_BUTTON_UP], pipbmp[PIPBOY_FRM_LITTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_red_butt_press, gsound_red_butt_release); } eventCode += 1; } y += 27; } if (intent == PIPBOY_OPEN_INTENT_REST) { if (!critter_can_obj_dude_rest()) { trans_buf_to_buf( pipbmp[PIPBOY_FRM_LOGO], ginfo[PIPBOY_FRM_LOGO].width, ginfo[PIPBOY_FRM_LOGO].height, ginfo[PIPBOY_FRM_LOGO].width, scrn_buf + PIPBOY_WINDOW_WIDTH * 156 + 323, PIPBOY_WINDOW_WIDTH); int month; int day; int year; game_time_date(&month, &day, &year); int holiday = 0; for (; holiday < HOLIDAY_COUNT; holiday += 1) { const HolidayDescription* holidayDescription = &(SpclDate[holiday]); if (holidayDescription->month == month && holidayDescription->day == day) { break; } } if (holiday != HOLIDAY_COUNT) { const HolidayDescription* holidayDescription = &(SpclDate[holiday]); const char* holidayName = getmsg(&pipboy_message_file, &pipmesg, holidayDescription->textId); char holidayNameCopy[256]; strcpy(holidayNameCopy, holidayName); int len = text_width(holidayNameCopy); text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * (ginfo[PIPBOY_FRM_LOGO].height + 174) + 6 + ginfo[PIPBOY_FRM_LOGO].width / 2 + 323 - len / 2, holidayNameCopy, 350, PIPBOY_WINDOW_WIDTH, colorTable[992]); } win_draw(pip_win); gsound_play_sfx_file("iisxxxx1"); const char* text = getmsg(&pipboy_message_file, &pipmesg, 215); dialog_out(text, NULL, 0, 192, 135, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); intent = PIPBOY_OPEN_INTENT_UNSPECIFIED; } } else { trans_buf_to_buf( pipbmp[PIPBOY_FRM_LOGO], ginfo[PIPBOY_FRM_LOGO].width, ginfo[PIPBOY_FRM_LOGO].height, ginfo[PIPBOY_FRM_LOGO].width, scrn_buf + PIPBOY_WINDOW_WIDTH * 156 + 323, PIPBOY_WINDOW_WIDTH); int month; int day; int year; game_time_date(&month, &day, &year); int holiday; for (holiday = 0; holiday < HOLIDAY_COUNT; holiday += 1) { const HolidayDescription* holidayDescription = &(SpclDate[holiday]); if (holidayDescription->month == month && holidayDescription->day == day) { break; } } if (holiday != HOLIDAY_COUNT) { const HolidayDescription* holidayDescription = &(SpclDate[holiday]); const char* holidayName = getmsg(&pipboy_message_file, &pipmesg, holidayDescription->textId); char holidayNameCopy[256]; strcpy(holidayNameCopy, holidayName); int length = text_width(holidayNameCopy); text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * (ginfo[PIPBOY_FRM_LOGO].height + 174) + 6 + ginfo[PIPBOY_FRM_LOGO].width / 2 + 323 - length / 2, holidayNameCopy, 350, PIPBOY_WINDOW_WIDTH, colorTable[992]); } win_draw(pip_win); } if (quest_init() == -1) { return -1; } gsound_play_sfx_file("pipon"); win_draw(pip_win); return intent; } // 0x497828 static void EndPipboy() { bool showScriptMessages = false; configGetBool(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages); if (showScriptMessages) { debug_printf("\nScript "); } scr_exec_map_update_scripts(); win_delete(pip_win); message_exit(&pipboy_message_file); // NOTE: Uninline. holodisks_exit(); for (int index = 0; index < PIPBOY_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } NixHotLines(); text_font(savefont); if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); enable_box_bar_win(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); intface_redraw(); // NOTE: Uninline. quest_exit(); } // 0x497918 void pip_init() { } // 0x49791C static void pip_num(int value, int digits, int x, int y) { int offset = PIPBOY_WINDOW_WIDTH * y + x + 9 * (digits - 1); for (int index = 0; index < digits; index++) { buf_to_buf(pipbmp[PIPBOY_FRM_NUMBERS] + 9 * (value % 10), 9, 17, 360, scrn_buf + offset, PIPBOY_WINDOW_WIDTH); offset -= 9; value /= 10; } } // 0x4979B4 static void pip_date() { int day; int month; int year; game_time_date(&month, &day, &year); pip_num(day, 2, PIPBOY_WINDOW_DAY_X, PIPBOY_WINDOW_DAY_Y); buf_to_buf(pipbmp[PIPBOY_FRM_MONTHS] + 435 * (month - 1), 29, 14, 29, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_MONTH_Y + PIPBOY_WINDOW_MONTH_X, PIPBOY_WINDOW_WIDTH); pip_num(year, 4, PIPBOY_WINDOW_YEAR_X, PIPBOY_WINDOW_YEAR_Y); } // 0x497A40 static void pip_print(const char* text, int flags, int color) { if ((flags & PIPBOY_TEXT_STYLE_UNDERLINE) != 0) { color |= FONT_UNDERLINE; } int left = 8; if ((flags & PIPBOY_TEXT_NO_INDENT) != 0) { left -= 7; } int length = text_width(text); if ((flags & PIPBOY_TEXT_ALIGNMENT_CENTER) != 0) { left = (350 - length) / 2; } else if ((flags & PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN) != 0) { left += 175; } else if ((flags & PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER) != 0) { left += 86 - length + 16; } else if ((flags & PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER) != 0) { left += 260 - length; } text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * (cursor_line * text_height() + PIPBOY_WINDOW_CONTENT_VIEW_Y) + PIPBOY_WINDOW_CONTENT_VIEW_X + left, text, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_WIDTH, color); if ((flags & PIPBOY_TEXT_STYLE_STRIKE_THROUGH) != 0) { int top = cursor_line * text_height() + 49; draw_line(scrn_buf, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_X + left, top, PIPBOY_WINDOW_CONTENT_VIEW_X + left + length, top, color); } if (cursor_line < bottom_line) { cursor_line += 1; } } // 0x497B64 static void pip_back(int color) { if (bottom_line >= 0) { cursor_line = bottom_line; } buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); // BACK const char* text = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(text, PIPBOY_TEXT_ALIGNMENT_CENTER, color); } // NOTE: Collapsed. // // 0x497BD4 int _save_pipboy(File* stream) { return 0; } // NOTE: Uncollapsed 0x497BD4. int save_pipboy(File* stream) { return _save_pipboy(stream); } // NOTE: Uncollapsed 0x497BD4. int load_pipboy(File* stream) { return _save_pipboy(stream); } // 0x497BD8 static void PipStatus(int a1) { if (a1 == 1024) { NixHotLines(); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } holo_flag = 0; holodisk = -1; holocount = 0; view_page = 0; stat_flag = 0; for (int index = 0; index < holodisks_count; index += 1) { HolodiskDescription* holodisk_entry = &(holodisks[index]); if (game_global_vars[holodisk_entry->gvar] != 0) { holocount += 1; break; } } ListStatLines(-1); if (statcount == 0) { const char* text = getmsg(&pipboy_message_file, &pipmesg, 203); pip_print(text, 0, colorTable[992]); } holocount = ListHoloDiskTitles(-1); win_draw_rect(pip_win, &pip_rect); AddHotLines(2, statcount + holocount + 1, false); win_draw(pip_win); return; } if (stat_flag == 0 && holo_flag == 0) { if (statcount != 0 && mouse_x < 429) { gsound_play_sfx_file("ib1p1xx1"); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); ListStatLines(a1); ListHoloDiskTitles(-1); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); stat_flag = 1; } else { if (holocount != 0 && holocount >= a1 && mouse_x > 429) { gsound_play_sfx_file("ib1p1xx1"); holodisk = 0; int index = 0; for (; index < holodisks_count; index += 1) { HolodiskDescription* holodisk_entry = &(holodisks[index]); if (game_global_vars[holodisk_entry->gvar] > 0) { if (a1 - 1 == holodisk) { break; } holodisk += 1; } } holodisk = index; buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); ListHoloDiskTitles(holodisk); ListStatLines(-1); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); NixHotLines(); ShowHoloDisk(); AddHotLines(0, 0, true); holo_flag = 1; } } } if (stat_flag == 0) { if (holo_flag == 0 || a1 < 1025 || a1 > 1027) { return; } if ((mouse_x > 459 && a1 != 1027) || a1 == 1026) { if (holopages <= view_page) { if (a1 != 1026) { gsound_play_sfx_file("ib1p1xx1"); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = bottom_line; } // Back const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]); if (bottom_line >= 0) { cursor_line = bottom_line; } // Done const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 214); pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); PipStatus(1024); } } else { gsound_play_sfx_file("ib1p1xx1"); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = bottom_line; } // Back const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]); if (bottom_line >= 0) { cursor_line = bottom_line; } // More const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 200); pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); view_page += 1; ShowHoloDisk(); } return; } if (a1 == 1027) { gsound_play_sfx_file("ib1p1xx1"); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = bottom_line; } // Back const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]); if (bottom_line >= 0) { cursor_line = bottom_line; } // More const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 200); pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); view_page -= 1; if (view_page < 0) { PipStatus(1024); return; } } else { if (mouse_x > 395) { return; } gsound_play_sfx_file("ib1p1xx1"); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * 436 + 254, 350, 20, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * 436 + 254, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = bottom_line; } // Back const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(text1, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]); if (bottom_line >= 0) { cursor_line = bottom_line; } // More const char* text2 = getmsg(&pipboy_message_file, &pipmesg, 200); pip_print(text2, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); if (view_page <= 0) { PipStatus(1024); return; } view_page -= 1; } ShowHoloDisk(); return; } if (a1 == 1025) { gsound_play_sfx_file("ib1p1xx1"); pip_back(colorTable[32747]); win_draw_rect(pip_win, &pip_rect); pause_for_tocks(200); PipStatus(1024); } if (a1 <= statcount) { gsound_play_sfx_file("ib1p1xx1"); int v13 = 0; int index = 0; for (; index < quest_count; index++) { QuestDescription* questDescription = &(quests[index]); if (questDescription->displayThreshold <= game_global_vars[questDescription->gvar]) { if (v13 == a1 - 1) { break; } v13 += 1; // Skip quests in the same location. // // FIXME: This code should be identical to the one in the // `ListStatLines`. See buffer overread // bug involved. for (; index < quest_count; index++) { if (quests[index].location != quests[index + 1].location) { break; } } } } NixHotLines(); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } if (bottom_line >= 1) { cursor_line = 1; } AddHotLines(0, 0, true); QuestDescription* questDescription = &(quests[index]); const char* text1 = getmsg(&pipboy_message_file, &pipmesg, 210); const char* text2 = getmsg(&map_msg_file, &pipmesg, questDescription->location); char formattedText[1024]; sprintf(formattedText, "%s %s", text2, text1); pip_print(formattedText, PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); if (bottom_line >= 3) { cursor_line = 3; } int number = 1; for (; index < quest_count; index++) { QuestDescription* questDescription = &(quests[index]); if (game_global_vars[questDescription->gvar] >= questDescription->displayThreshold) { const char* text = getmsg(&quest_message_file, &pipmesg, questDescription->description); char formattedText[1024]; sprintf(formattedText, "%d. %s", number, text); number += 1; short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (word_wrap(formattedText, 350, beginnings, &count) == 0) { for (int line = 0; line < count - 1; line += 1) { char* beginning = formattedText + beginnings[line]; char* ending = formattedText + beginnings[line + 1]; char c = *ending; *ending = '\0'; int flags; int color; if (game_global_vars[questDescription->gvar] < questDescription->completedThreshold) { flags = 0; color = colorTable[992]; } else { flags = PIPBOY_TEXT_STYLE_STRIKE_THROUGH; color = colorTable[8804]; } pip_print(beginning, flags, color); *ending = c; cursor_line += 1; } } else { debug_printf("\n ** Word wrap error in pipboy! **\n"); } } if (index != quest_count - 1) { QuestDescription* nextQuestDescription = &(quests[index + 1]); if (questDescription->location != nextQuestDescription->location) { break; } } } pip_back(colorTable[992]); win_draw_rect(pip_win, &pip_rect); stat_flag = 1; } } // [a1] is likely selected location, or -1 if nothing is selected // // 0x498734 static void ListStatLines(int a1) { if (bottom_line >= 0) { cursor_line = 0; } int flags = holocount != 0 ? PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER : PIPBOY_TEXT_ALIGNMENT_CENTER; flags |= PIPBOY_TEXT_STYLE_UNDERLINE; // STATUS const char* statusText = getmsg(&pipboy_message_file, &pipmesg, 202); pip_print(statusText, flags, colorTable[992]); if (bottom_line >= 2) { cursor_line = 2; } statcount = 0; for (int index = 0; index < quest_count; index += 1) { QuestDescription* quest = &(quests[index]); if (quest->displayThreshold > game_global_vars[quest->gvar]) { continue; } int color = (cursor_line - 1) / 2 == (a1 - 1) ? colorTable[32747] : colorTable[992]; // Render location. const char* questLocation = getmsg(&map_msg_file, &pipmesg, quest->location); pip_print(questLocation, 0, color); cursor_line += 1; statcount += 1; // Skip quests in the same location. // // FIXME: There is a buffer overread bug at the end of the loop. It does // not manifest because dynamically allocated memory blocks have special // footer guard. Location field is the first in the struct and matches // size of the guard. So on the final iteration it compares location of // the last quest with this special guard (0xBEEFCAFE). for (; index < quest_count; index++) { if (quests[index].location != quests[index + 1].location) { break; } } } } // 0x4988A0 static void ShowHoloDisk() { buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } HolodiskDescription* holodisk_entry = &(holodisks[holodisk]); int holodiskTextId; int linesCount = 0; holopages = 0; for (holodiskTextId = holodisk_entry->description; holodiskTextId < holodisk_entry->description + 500; holodiskTextId += 1) { const char* text = getmsg(&pipboy_message_file, &pipmesg, holodiskTextId); if (strcmp(text, "**END-DISK**") == 0) { break; } linesCount += 1; if (linesCount >= PIPBOY_HOLODISK_LINES_MAX) { linesCount = 0; holopages += 1; } } if (holodiskTextId >= holodisk_entry->description + 500) { debug_printf("\nPIPBOY: #1 Holodisk text end not found!\n"); } holodiskTextId = holodisk_entry->description; if (view_page != 0) { int page = 0; int numberOfLines = 0; for (; holodiskTextId < holodiskTextId + 500; holodiskTextId += 1) { const char* line = getmsg(&pipboy_message_file, &pipmesg, holodiskTextId); if (strcmp(line, "**END-DISK**") == 0) { debug_printf("\nPIPBOY: Premature page end in holodisk page search!\n"); break; } numberOfLines += 1; if (numberOfLines >= PIPBOY_HOLODISK_LINES_MAX) { page += 1; if (page >= view_page) { break; } numberOfLines = 0; } } holodiskTextId += 1; if (holodiskTextId >= holodisk_entry->description + 500) { debug_printf("\nPIPBOY: #2 Holodisk text end not found!\n"); } } else { const char* name = getmsg(&pipboy_message_file, &pipmesg, holodisk_entry->name); pip_print(name, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); } if (holopages != 0) { // of const char* of = getmsg(&pipboy_message_file, &pipmesg, 212); char formattedText[60]; // TODO: Size is probably wrong. sprintf(formattedText, "%d %s %d", view_page + 1, of, holopages + 1); int len = text_width(of); text_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * 47 + 616 + 604 - len, formattedText, 350, PIPBOY_WINDOW_WIDTH, colorTable[992]); } if (bottom_line >= 3) { cursor_line = 3; } for (int line = 0; line < PIPBOY_HOLODISK_LINES_MAX; line += 1) { const char* text = getmsg(&pipboy_message_file, &pipmesg, holodiskTextId); if (strcmp(text, "**END-DISK**") == 0) { break; } if (strcmp(text, "**END-PAR**") == 0) { cursor_line += 1; } else { pip_print(text, PIPBOY_TEXT_NO_INDENT, colorTable[992]); } holodiskTextId += 1; } int moreOrDoneTextId; if (holopages <= view_page) { if (bottom_line >= 0) { cursor_line = bottom_line; } const char* back = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(back, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]); if (bottom_line >= 0) { cursor_line = bottom_line; } moreOrDoneTextId = 214; } else { if (bottom_line >= 0) { cursor_line = bottom_line; } const char* back = getmsg(&pipboy_message_file, &pipmesg, 201); pip_print(back, PIPBOY_TEXT_ALIGNMENT_LEFT_COLUMN_CENTER, colorTable[992]); if (bottom_line >= 0) { cursor_line = bottom_line; } moreOrDoneTextId = 200; } const char* moreOrDoneText = getmsg(&pipboy_message_file, &pipmesg, moreOrDoneTextId); pip_print(moreOrDoneText, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER, colorTable[992]); win_draw(pip_win); } // 0x498C40 static int ListHoloDiskTitles(int a1) { if (bottom_line >= 2) { cursor_line = 2; } int knownHolodisksCount = 0; for (int index = 0; index < holodisks_count; index++) { HolodiskDescription* holodisk_entry = &(holodisks[index]); if (game_global_vars[holodisk_entry->gvar] != 0) { int color; if ((cursor_line - 2) / 2 == a1) { color = colorTable[32747]; } else { color = colorTable[992]; } const char* text = getmsg(&pipboy_message_file, &pipmesg, holodisk_entry->name); pip_print(text, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN, color); cursor_line++; knownHolodisksCount++; } } if (knownHolodisksCount != 0) { if (bottom_line >= 0) { cursor_line = 0; } const char* text = getmsg(&pipboy_message_file, &pipmesg, 211); // DATA pip_print(text, PIPBOY_TEXT_ALIGNMENT_RIGHT_COLUMN_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); } return knownHolodisksCount; } // 0x498D34 static int qscmp(const void* a1, const void* a2) { STRUCT_664350* v1 = (STRUCT_664350*)a1; STRUCT_664350* v2 = (STRUCT_664350*)a2; return strcmp(v1->name, v2->name); } // 0x498D40 static void PipAutomaps(int a1) { if (a1 == 1024) { NixHotLines(); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } const char* title = getmsg(&pipboy_message_file, &pipmesg, 205); pip_print(title, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); actcnt = PrintAMList(-1); AddHotLines(2, actcnt, 0); win_draw_rect(pip_win, &pip_rect); amlst_mode = 0; return; } if (amlst_mode != 0) { if (a1 == 1025 || a1 <= -1) { PipAutomaps(1024); gsound_play_sfx_file("ib1p1xx1"); } if (a1 >= 1 && a1 <= actcnt + 3) { gsound_play_sfx_file("ib1p1xx1"); PrintAMelevList(a1); draw_top_down_map_pipboy(pip_win, sortlist[a1 - 1].field_6, sortlist[a1 - 1].field_4); win_draw_rect(pip_win, &pip_rect); } return; } if (a1 > 0 && a1 <= actcnt) { gsound_play_sfx_file("ib1p1xx1"); NixHotLines(); PrintAMList(a1); win_draw_rect(pip_win, &pip_rect); amcty_indx = sortlist[a1 - 1].field_4; actcnt = PrintAMelevList(1); AddHotLines(0, actcnt + 2, 1); draw_top_down_map_pipboy(pip_win, sortlist[0].field_6, sortlist[0].field_4); win_draw_rect(pip_win, &pip_rect); amlst_mode = 1; } } // 0x498F30 static int PrintAMelevList(int a1) { AutomapHeader* automapHeader; if (ReadAMList(&automapHeader) == -1) { return -1; } int v4 = 0; for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { if (automapHeader->offsets[amcty_indx][elevation] > 0) { sortlist[v4].name = map_get_elev_idx(amcty_indx, elevation); sortlist[v4].field_4 = elevation; sortlist[v4].field_6 = amcty_indx; v4++; } } int mapCount = wmMapMaxCount(); for (int map = 0; map < mapCount; map++) { if (map == amcty_indx) { continue; } if (get_map_idx_same(amcty_indx, map) == -1) { continue; } for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { if (automapHeader->offsets[map][elevation] > 0) { sortlist[v4].name = map_get_elev_idx(map, elevation); sortlist[v4].field_4 = elevation; sortlist[v4].field_6 = map; v4++; } } } buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } const char* msg = getmsg(&pipboy_message_file, &pipmesg, 205); pip_print(msg, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); if (bottom_line >= 2) { cursor_line = 2; } const char* name = map_get_description_idx(amcty_indx); pip_print(name, PIPBOY_TEXT_ALIGNMENT_CENTER, colorTable[992]); if (bottom_line >= 4) { cursor_line = 4; } int selectedPipboyLine = (a1 - 1) * 2; for (int index = 0; index < v4; index++) { int color; if (cursor_line - 4 == selectedPipboyLine) { color = colorTable[32747]; } else { color = colorTable[992]; } pip_print(sortlist[index].name, 0, color); cursor_line++; } pip_back(colorTable[992]); return v4; } // 0x499150 static int PrintAMList(int a1) { AutomapHeader* automapHeader; if (ReadAMList(&automapHeader) == -1) { return -1; } int count = 0; int index = 0; int mapCount = wmMapMaxCount(); for (int map = 0; map < mapCount; map++) { int elevation; for (elevation = 0; elevation < ELEVATION_COUNT; elevation++) { if (automapHeader->offsets[map][elevation] > 0) { if (automapDisplayMap(map) == 0) { break; } } } if (elevation < ELEVATION_COUNT) { int v7; if (count != 0) { v7 = 0; for (int index = 0; index < count; index++) { if (is_map_idx_same(map, sortlist[index].field_4)) { break; } v7++; } } else { v7 = 0; } if (v7 == count) { sortlist[count].name = map_get_short_name(map); sortlist[count].field_4 = map; count++; } } } if (count != 0) { if (count > 1) { qsort(sortlist, count, sizeof(*sortlist), qscmp); } buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } const char* msg = getmsg(&pipboy_message_file, &pipmesg, 205); pip_print(msg, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); if (bottom_line >= 2) { cursor_line = 2; } for (int index = 0; index < count; index++) { int color; if (cursor_line - 1 == a1) { color = colorTable[32747]; } else { color = colorTable[992]; } pip_print(sortlist[index].name, 0, color); cursor_line++; } } return count; } // 0x49932C static void PipArchives(int a1) { if (a1 == 1024) { NixHotLines(); view_page = ListArchive(-1); AddHotLines(2, view_page, false); } else if (a1 >= 0 && a1 <= view_page) { gsound_play_sfx_file("ib1p1xx1"); ListArchive(a1); int movie; for (movie = 2; movie < 16; movie++) { if (gmovie_has_been_played(movie)) { a1--; if (a1 <= 0) { break; } } } if (movie <= MOVIE_COUNT) { gmovie_play(movie, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC); } else { debug_printf("\n ** Selected movie not found in list! **\n"); } text_font(101); wait_time = get_time(); ListArchive(-1); } } // 0x4993DC static int ListArchive(int a1) { const char* text; int i; int v12; int msg_num; int v5; int v8; int v9; buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } // VIDEO ARCHIVES text = getmsg(&pipboy_message_file, &pipmesg, 206); pip_print(text, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); if (bottom_line >= 2) { cursor_line = 2; } v5 = 0; v12 = a1 - 1; // 502 - Elder Speech // ... // 516 - Credits msg_num = 502; for (i = 2; i < 16; i++) { if (gmovie_has_been_played(i)) { v8 = v5++; if (v8 == v12) { v9 = colorTable[32747]; } else { v9 = colorTable[992]; } text = getmsg(&pipboy_message_file, &pipmesg, msg_num); pip_print(text, 0, v9); cursor_line++; } msg_num++; } win_draw_rect(pip_win, &pip_rect); return v5; } // 0x499518 static void PipAlarm(int a1) { if (a1 == 1024) { if (critter_can_obj_dude_rest()) { NixHotLines(); DrawAlarmText(0); AddHotLines(5, currentAlarmTypeCount, false); } else { gsound_play_sfx_file("iisxxxx1"); // You cannot rest at this location! const char* text = getmsg(&pipboy_message_file, &pipmesg, 215); dialog_out(text, NULL, 0, 192, 135, colorTable[32328], 0, colorTable[32328], DIALOG_BOX_LARGE); } } else if (a1 >= 4 && a1 <= 17) { gsound_play_sfx_file("ib1p1xx1"); DrawAlarmText(a1 - 3); int duration = a1 - 4; int minutes = 0; int hours = 0; int v10 = 0; switch (duration) { case PIPBOY_REST_DURATION_TEN_MINUTES: TimedRest(0, 10, 0); break; case PIPBOY_REST_DURATION_THIRTY_MINUTES: TimedRest(0, 30, 0); break; case PIPBOY_REST_DURATION_ONE_HOUR: case PIPBOY_REST_DURATION_TWO_HOURS: case PIPBOY_REST_DURATION_THREE_HOURS: case PIPBOY_REST_DURATION_FOUR_HOURS: case PIPBOY_REST_DURATION_FIVE_HOURS: case PIPBOY_REST_DURATION_SIX_HOURS: TimedRest(duration - 1, 0, 0); break; case PIPBOY_REST_DURATION_UNTIL_MORNING: ClacTime(&hours, &minutes, 8); TimedRest(hours, minutes, 0); break; case PIPBOY_REST_DURATION_UNTIL_NOON: ClacTime(&hours, &minutes, 12); TimedRest(hours, minutes, 0); break; case PIPBOY_REST_DURATION_UNTIL_EVENING: ClacTime(&hours, &minutes, 18); TimedRest(hours, minutes, 0); break; case PIPBOY_REST_DURATION_UNTIL_MIDNIGHT: ClacTime(&hours, &minutes, 0); if (TimedRest(hours, minutes, 0) == 0) { pip_num(0, 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); } win_draw(pip_win); break; case PIPBOY_REST_DURATION_UNTIL_HEALED: case PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED: TimedRest(0, 0, duration); break; } gsound_play_sfx_file("ib2lu1x1"); DrawAlarmText(0); } } // 0x4996B4 static void DrawAlarmText(int a1) { const char* text; buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); if (bottom_line >= 0) { cursor_line = 0; } // ALARM CLOCK text = getmsg(&pipboy_message_file, &pipmesg, 300); pip_print(text, PIPBOY_TEXT_ALIGNMENT_CENTER | PIPBOY_TEXT_STYLE_UNDERLINE, colorTable[992]); if (bottom_line >= 5) { cursor_line = 5; } DrawAlrmHitPnts(); // NOTE: I don't know if this +1 was a result of compiler optimization or it // was written like this in the first place. for (int option = 1; option < currentAlarmTypeCount + 1; option++) { // 302 - Rest for ten minutes // ... // 315 - Rest until party is healed text = getmsg(&pipboy_message_file, &pipmesg, 302 + option - 1); int color = option == a1 ? colorTable[32747] : colorTable[992]; pip_print(text, 0, color); cursor_line++; } win_draw_rect(pip_win, &pip_rect); } // 0x4997B8 static void DrawAlrmHitPnts() { int max_hp; int cur_hp; char* text; char msg[64]; int len; buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + 66 * PIPBOY_WINDOW_WIDTH + 254, 350, 10, PIPBOY_WINDOW_WIDTH, scrn_buf + 66 * PIPBOY_WINDOW_WIDTH + 254, PIPBOY_WINDOW_WIDTH); max_hp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); cur_hp = critter_get_hits(obj_dude); text = getmsg(&pipboy_message_file, &pipmesg, 301); // Hit Points sprintf(msg, "%s %d/%d", text, cur_hp, max_hp); len = text_width(msg); text_to_buf(scrn_buf + 66 * PIPBOY_WINDOW_WIDTH + 254 + (350 - len) / 2, msg, PIPBOY_WINDOW_WIDTH, PIPBOY_WINDOW_WIDTH, colorTable[992]); } // 0x4998C0 static void AddHotLines(int start, int count, bool add_back_button) { text_font(101); int height = text_height(); hot_line_start = start; hot_line_count = count; if (count != 0) { int y = start * height + PIPBOY_WINDOW_CONTENT_VIEW_Y; int eventCode = start + 505; for (int index = start; index < 22; index++) { if (hot_line_count + hot_line_start <= index) { break; } HotLines[index] = win_register_button(pip_win, 254, y, 350, height, -1, -1, -1, eventCode, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); y += height * 2; eventCode += 1; } } if (add_back_button) { HotLines[BACK_BUTTON_INDEX] = win_register_button(pip_win, 254, height * bottom_line + PIPBOY_WINDOW_CONTENT_VIEW_Y, 350, height, -1, -1, -1, 528, NULL, NULL, NULL, BUTTON_FLAG_TRANSPARENT); } } // 0x4999C0 static void NixHotLines() { if (hot_line_count != 0) { // NOTE: There is a buffer overread bug. In original binary it leads to // reading continuation (from 0x6644B8 onwards), which finally destroys // button in `pip_win` (id #3), which corresponds to Skilldex // button. Other buttons can be destroyed depending on the last mouse // position. I was not able to replicate this exact behaviour with MSVC. // So here is a small fix, which is an exception to "Do not fix vanilla // bugs" strategy. // // TODO: Reevaluate after merging back button into `HotLines`. int end = hot_line_start + hot_line_count; if (end > 22) { end = 22; } for (int index = hot_line_start; index < end; index++) { win_delete_button(HotLines[index]); } } if (hot_back_line) { win_delete_button(HotLines[BACK_BUTTON_INDEX]); } hot_line_count = 0; hot_back_line = 0; } // 0x499A24 static bool TimedRest(int hours, int minutes, int duration) { gmouse_set_cursor(MOUSE_CURSOR_WAIT_WATCH); bool rc = false; if (duration == 0) { int hoursInMinutes = hours * 60; double v1 = (double)hoursInMinutes + (double)minutes; double v2 = v1 * (1.0 / 1440.0) * 3.5 + 0.25; double v3 = (double)minutes / v1 * v2; if (minutes != 0) { int gameTime = game_time(); double v4 = v3 * 20.0; int v5 = 0; for (int v5 = 0; v5 < (int)v4; v5++) { if (rc) { break; } unsigned int start = get_time(); unsigned int v6 = (unsigned int)((double)v5 / v4 * ((double)minutes * 600.0) + (double)gameTime); unsigned int nextEventTime = queue_next_time(); if (v6 >= nextEventTime) { gameTimeSetTime(nextEventTime + 1); if (queue_process()) { rc = true; debug_printf("PIPBOY: Returning from Queue trigger...\n"); proc_bail_flag = 1; break; } if (game_user_wants_to_quit != 0) { rc = true; } } if (!rc) { gameTimeSetTime(v6); if (get_input() == KEY_ESCAPE || game_user_wants_to_quit != 0) { rc = true; } pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); pip_date(); win_draw(pip_win); while (elapsed_time(start) < 50) { } } } if (!rc) { gameTimeSetTime(gameTime + 600 * minutes); if (Check4Health(minutes)) { // NOTE: Uninline. AddHealth(); } } pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); pip_date(); DrawAlrmHitPnts(); win_draw(pip_win); } if (hours != 0 && !rc) { int gameTime = game_time(); double v7 = (v2 - v3) * 20.0; for (int hour = 0; hour < (int)v7; hour++) { if (rc) { break; } unsigned int start = get_time(); if (get_input() == KEY_ESCAPE || game_user_wants_to_quit != 0) { rc = true; } unsigned int v8 = (unsigned int)((double)hour / v7 * (hours * GAME_TIME_TICKS_PER_HOUR) + gameTime); unsigned int nextEventTime = queue_next_time(); if (!rc && v8 >= nextEventTime) { gameTimeSetTime(nextEventTime + 1); if (queue_process()) { rc = true; debug_printf("PIPBOY: Returning from Queue trigger...\n"); proc_bail_flag = 1; break; } if (game_user_wants_to_quit != 0) { rc = true; } } if (!rc) { gameTimeSetTime(v8); int healthToAdd = (int)((double)hoursInMinutes / v7); if (Check4Health(healthToAdd)) { // NOTE: Uninline. AddHealth(); } pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); pip_date(); DrawAlrmHitPnts(); win_draw(pip_win); while (elapsed_time(start) < 50) { } } } if (!rc) { gameTimeSetTime(gameTime + GAME_TIME_TICKS_PER_HOUR * hours); } pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); pip_date(); DrawAlrmHitPnts(); win_draw(pip_win); } } else if (duration == PIPBOY_REST_DURATION_UNTIL_HEALED || duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) { int currentHp = critter_get_hits(obj_dude); int maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); if (currentHp != maxHp || (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED && partyMemberNeedsHealing())) { // First pass - healing dude is the top priority. int hpToHeal = maxHp - currentHp; int healingRate = critterGetStat(obj_dude, STAT_HEALING_RATE); int hoursToHeal = (int)((double)hpToHeal / (double)healingRate * 3.0); while (!rc && hoursToHeal != 0) { if (hoursToHeal <= 24) { rc = TimedRest(hoursToHeal, 0, 0); hoursToHeal = 0; } else { rc = TimedRest(24, 0, 0); hoursToHeal -= 24; } } // Second pass - attempt to heal delayed damage to dude (via poison // or radiation), and remaining party members. This process is // performed in 3 hour increments. currentHp = critter_get_hits(obj_dude); maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); hpToHeal = maxHp - currentHp; if (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) { int partyHpToHeal = partyMemberMaxHealingNeeded(); if (partyHpToHeal > hpToHeal) { hpToHeal = partyHpToHeal; } } while (!rc && hpToHeal != 0) { currentHp = critter_get_hits(obj_dude); maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); hpToHeal = maxHp - currentHp; if (duration == PIPBOY_REST_DURATION_UNTIL_PARTY_HEALED) { int partyHpToHeal = partyMemberMaxHealingNeeded(); if (partyHpToHeal > hpToHeal) { hpToHeal = partyHpToHeal; } } rc = TimedRest(3, 0, 0); } } else { // No one needs healing. gmouse_set_cursor(MOUSE_CURSOR_ARROW); return rc; } } int gameTime = game_time(); int nextEventGameTime = queue_next_time(); if (gameTime > nextEventGameTime) { if (queue_process()) { debug_printf("PIPBOY: Returning from Queue trigger...\n"); proc_bail_flag = 1; rc = true; } } pip_num(game_time_hour(), 4, PIPBOY_WINDOW_TIME_X, PIPBOY_WINDOW_TIME_Y); pip_date(); win_draw(pip_win); gmouse_set_cursor(MOUSE_CURSOR_ARROW); return rc; } // 0x499FCC static bool Check4Health(int a1) { rest_time += a1; if (rest_time < 180) { return false; } debug_printf("\n health added!\n"); rest_time = 0; return true; } // NOTE: Inlined. // // 0x49A008 static bool AddHealth() { partyMemberRestingHeal(3); int currentHp = critter_get_hits(obj_dude); int maxHp = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); return currentHp == maxHp; } // Returns [hours] and [minutes] needed to rest until [wakeUpHour]. // // 0x49A03C static void ClacTime(int* hours, int* minutes, int wakeUpHour) { int gameTimeHour = game_time_hour(); *hours = gameTimeHour / 100; *minutes = gameTimeHour % 100; if (*hours != wakeUpHour || *minutes != 0) { *hours = wakeUpHour - *hours; if (*hours < 0) { *hours += 24; if (*minutes != 0) { *hours -= 1; *minutes = 60 - *minutes; } } else { if (*minutes != 0) { *hours -= 1; *minutes = 60 - *minutes; if (*hours < 0) { *hours = 23; } } } } else { *hours = 24; } } // 0x49A0C8 static int ScreenSaver() { PipboyBomb bombs[PIPBOY_BOMB_COUNT]; mouse_get_position(&old_mouse_x, &old_mouse_y); for (int index = 0; index < PIPBOY_BOMB_COUNT; index += 1) { bombs[index].field_10 = 0; } gmouse_disable(0); unsigned char* buf = (unsigned char*)mem_malloc(412 * 374); if (buf == NULL) { return -1; } buf_to_buf(scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, buf, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH); buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); int v31 = 50; while (true) { unsigned int time = get_time(); mouse_get_position(&mouse_x, &mouse_y); if (get_input() != -1 || old_mouse_x != mouse_x || old_mouse_y != mouse_y) { break; } double random = roll_random(0, PIPBOY_RAND_MAX); // TODO: Figure out what this constant means. Probably somehow related // to PIPBOY_RAND_MAX. if (random < 3047.3311) { int index = 0; for (; index < PIPBOY_BOMB_COUNT; index += 1) { if (bombs[index].field_10 == 0) { break; } } if (index < PIPBOY_BOMB_COUNT) { PipboyBomb* bomb = &(bombs[index]); int v27 = (350 - ginfo[PIPBOY_FRM_BOMB].width / 4) + (406 - ginfo[PIPBOY_FRM_BOMB].height / 4); int v5 = (int)((double)roll_random(0, PIPBOY_RAND_MAX) / (double)PIPBOY_RAND_MAX * (double)v27); int v6 = ginfo[PIPBOY_FRM_BOMB].height / 4; if (PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT - v6 >= v5) { bomb->x = 602; bomb->y = v5 + 48; } else { bomb->x = v5 - (PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT - v6) + PIPBOY_WINDOW_CONTENT_VIEW_X + ginfo[PIPBOY_FRM_BOMB].width / 4; bomb->y = PIPBOY_WINDOW_CONTENT_VIEW_Y - ginfo[PIPBOY_FRM_BOMB].height + 2; } bomb->field_10 = 1; bomb->field_8 = (float)((double)roll_random(0, PIPBOY_RAND_MAX) * (2.75 / PIPBOY_RAND_MAX) + 0.15); bomb->field_C = 0; } } if (v31 == 0) { buf_to_buf(pipbmp[PIPBOY_FRM_BACKGROUND] + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); } for (int index = 0; index < PIPBOY_BOMB_COUNT; index++) { PipboyBomb* bomb = &(bombs[index]); if (bomb->field_10 != 1) { continue; } int srcWidth = ginfo[PIPBOY_FRM_BOMB].width; int srcHeight = ginfo[PIPBOY_FRM_BOMB].height; int destX = bomb->x; int destY = bomb->y; int srcY = 0; int srcX = 0; if (destX >= PIPBOY_WINDOW_CONTENT_VIEW_X) { if (destX + ginfo[PIPBOY_FRM_BOMB].width >= 604) { srcWidth = 604 - destX; if (srcWidth < 1) { bomb->field_10 = 0; } } } else { srcX = PIPBOY_WINDOW_CONTENT_VIEW_X - destX; if (srcX >= ginfo[PIPBOY_FRM_BOMB].width) { bomb->field_10 = 0; } destX = PIPBOY_WINDOW_CONTENT_VIEW_X; srcWidth = ginfo[PIPBOY_FRM_BOMB].width - srcX; } if (destY >= PIPBOY_WINDOW_CONTENT_VIEW_Y) { if (destY + ginfo[PIPBOY_FRM_BOMB].height >= 452) { srcHeight = 452 - destY; if (srcHeight < 1) { bomb->field_10 = 0; } } } else { if (destY + ginfo[PIPBOY_FRM_BOMB].height < PIPBOY_WINDOW_CONTENT_VIEW_Y) { bomb->field_10 = 0; } srcY = PIPBOY_WINDOW_CONTENT_VIEW_Y - destY; srcHeight = ginfo[PIPBOY_FRM_BOMB].height - srcY; destY = PIPBOY_WINDOW_CONTENT_VIEW_Y; } if (bomb->field_10 == 1 && v31 == 0) { trans_buf_to_buf( pipbmp[PIPBOY_FRM_BOMB] + ginfo[PIPBOY_FRM_BOMB].width * srcY + srcX, srcWidth, srcHeight, ginfo[PIPBOY_FRM_BOMB].width, scrn_buf + PIPBOY_WINDOW_WIDTH * destY + destX, PIPBOY_WINDOW_WIDTH); } bomb->field_C += bomb->field_8; if (bomb->field_C >= 1.0) { bomb->x = (int)((float)bomb->x - bomb->field_C); bomb->y = (int)((float)bomb->y + bomb->field_C); bomb->field_C = 0.0; } } if (v31 != 0) { v31 -= 1; } else { win_draw_rect(pip_win, &pip_rect); while (elapsed_time(time) < 50) { } } } buf_to_buf(buf, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, PIPBOY_WINDOW_CONTENT_VIEW_HEIGHT, PIPBOY_WINDOW_CONTENT_VIEW_WIDTH, scrn_buf + PIPBOY_WINDOW_WIDTH * PIPBOY_WINDOW_CONTENT_VIEW_Y + PIPBOY_WINDOW_CONTENT_VIEW_X, PIPBOY_WINDOW_WIDTH); mem_free(buf); win_draw_rect(pip_win, &pip_rect); gmouse_enable(); return 0; } // 0x49A5D4 static int quest_init() { // NOTE: Uninline. quest_exit(); if (!message_init(&quest_message_file)) { return -1; } if (!message_load(&quest_message_file, "game\\quests.msg")) { return -1; } File* stream = db_fopen("data\\quests.txt", "rt"); if (stream == NULL) { return -1; } char string[256]; while (db_fgets(string, 256, stream)) { const char* delim = " \t,"; char* tok; QuestDescription entry; char* pch = string; while (isspace(*pch)) { pch++; } if (*pch == '#') { continue; } tok = strtok(pch, delim); if (tok == NULL) { continue; } entry.location = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.description = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.gvar = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.displayThreshold = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.completedThreshold = atoi(tok); QuestDescription* entries = (QuestDescription*)mem_realloc(quests, sizeof(*quests) * (quest_count + 1)); if (entries == NULL) { goto err; } memcpy(&(entries[quest_count]), &entry, sizeof(entry)); quests = entries; quest_count++; } qsort(quests, quest_count, sizeof(*quests), quest_qsort_compare); db_fclose(stream); return 0; err: db_fclose(stream); return -1; } // 0x49A7E4 static void quest_exit() { if (quests != NULL) { mem_free(quests); quests = NULL; } quest_count = 0; message_exit(&quest_message_file); } // 0x49A818 static int quest_qsort_compare(const void* a1, const void* a2) { QuestDescription* quest1 = (QuestDescription*)a1; QuestDescription* quest2 = (QuestDescription*)a2; return quest1->location - quest2->location; } // 0x49A824 static int holodisks_init() { // NOTE: Uninline. holodisks_exit(); File* stream = db_fopen("data\\holodisk.txt", "rt"); if (stream == NULL) { return -1; } char str[256]; while (db_fgets(str, sizeof(str), stream)) { const char* delim = " \t,"; char* tok; HolodiskDescription entry; char* ch = str; while (isspace(*ch)) { ch++; } if (*ch == '#') { continue; } tok = strtok(ch, delim); if (tok == NULL) { continue; } entry.gvar = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.name = atoi(tok); tok = strtok(NULL, delim); if (tok == NULL) { continue; } entry.description = atoi(tok); HolodiskDescription* entries = (HolodiskDescription*)mem_realloc(holodisks, sizeof(*holodisks) * (holodisks_count + 1)); if (entries == NULL) { goto err; } memcpy(&(entries[holodisks_count]), &entry, sizeof(*holodisks)); holodisks = entries; holodisks_count++; } db_fclose(stream); return 0; err: db_fclose(stream); return -1; } // 0x49A968 static void holodisks_exit() { if (holodisks != NULL) { mem_free(holodisks); holodisks = NULL; } holodisks_count = 0; } ================================================ FILE: src/game/pipboy.h ================================================ #ifndef FALLOUT_GAME_PIPBOY_H_ #define FALLOUT_GAME_PIPBOY_H_ #include #include "game/art.h" #include "plib/db/db.h" #include "plib/gnw/rect.h" #include "game/message.h" typedef enum PipboyOpenIntent { PIPBOY_OPEN_INTENT_UNSPECIFIED = 0, PIPBOY_OPEN_INTENT_REST = 1, } PipboyOpenIntent; typedef void(PipboyRenderProc)(int a1); PipboyRenderProc* PipFnctn[5]; int pipboy(int intent); void pip_init(); int save_pipboy(File* stream); int load_pipboy(File* stream); #endif /* FALLOUT_GAME_PIPBOY_H_ */ ================================================ FILE: src/game/protinst.c ================================================ #include "game/protinst.h" #include #include #include #include "game/anim.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/map.h" #include "game/message.h" #include "game/object.h" #include "game/palette.h" #include "game/perk.h" #include "game/proto.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/tile.h" #include "game/worldmap.h" static int obj_use_book(Object* item_obj); static int obj_use_flare(Object* critter_obj, Object* item_obj); static int obj_use_explosive(Object* explosive); static int obj_use_misc_item(Object* item_obj); static int protinstTestDroppedExplosive(Object* a1); static int protinst_default_use_item(Object* a1, Object* a2, Object* item); static int set_door_state_open(Object* a1, Object* a2); static int set_door_state_closed(Object* a1, Object* a2); static int check_door_state(Object* a1, Object* a2); // 0x49A9A0 int obj_sid(Object* object, int* sidPtr) { *sidPtr = object->sid; if (*sidPtr == -1) { return -1; } return 0; } // 0x49A9B4 int obj_new_sid(Object* object, int* sidPtr) { *sidPtr = -1; Proto* proto; if (proto_ptr(object->pid, &proto) == -1) { return -1; } int sid; int objectType = PID_TYPE(object->pid); if (objectType < OBJ_TYPE_TILE) { sid = proto->sid; } else if (objectType == OBJ_TYPE_TILE) { sid = proto->tile.sid; } else if (objectType == OBJ_TYPE_MISC) { sid = -1; } else { assert(false && "Should be unreachable"); } if (sid == -1) { return -1; } int scriptType = SID_TYPE(sid); if (scr_new(sidPtr, scriptType) == -1) { return -1; } Script* script; if (scr_ptr(*sidPtr, &script) == -1) { return -1; } script->field_14 = sid & 0xFFFFFF; if (objectType == OBJ_TYPE_CRITTER) { object->field_80 = script->field_14; } if (scriptType == SCRIPT_TYPE_SPATIAL) { script->sp.built_tile = builtTileCreate(object->tile, object->elevation); script->sp.radius = 3; } if (object->id == -1) { object->id = new_obj_id(); } script->field_1C = object->id; script->owner = object; scr_find_str_run_info(sid & 0xFFFFFF, &(script->field_50), *sidPtr); return 0; } // 0x49AAC0 int obj_new_sid_inst(Object* obj, int scriptType, int a3) { if (a3 == -1) { return -1; } int sid; if (scr_new(&sid, scriptType) == -1) { return -1; } Script* script; if (scr_ptr(sid, &script) == -1) { return -1; } script->field_14 = a3; if (scriptType == SCRIPT_TYPE_SPATIAL) { script->sp.built_tile = builtTileCreate(obj->tile, obj->elevation); script->sp.radius = 3; } obj->sid = sid; obj->id = new_obj_id(); script->field_1C = obj->id; script->owner = obj; scr_find_str_run_info(a3 & 0xFFFFFF, &(script->field_50), sid); if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { obj->field_80 = script->field_14; } return 0; } // 0x49AC3C int obj_look_at(Object* a1, Object* a2) { return obj_look_at_func(a1, a2, display_print); } // 0x49AC4C int obj_look_at_func(Object* a1, Object* a2, void (*a3)(char* string)) { if (critter_is_dead(a1)) { return -1; } if (FID_TYPE(a2->fid) == OBJ_TYPE_TILE) { return -1; } Proto* proto; if (proto_ptr(a2->pid, &proto) == -1) { return -1; } bool scriptOverrides = false; if (a2->sid != -1) { scr_set_objs(a2->sid, a1, a2); exec_script_proc(a2->sid, SCRIPT_PROC_LOOK_AT); Script* script; if (scr_ptr(a2->sid, &script) == -1) { return -1; } scriptOverrides = script->scriptOverrides; } if (!scriptOverrides) { MessageListItem messageListItem; if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER && critter_is_dead(a2)) { messageListItem.num = 491 + roll_random(0, 1); } else { messageListItem.num = 490; } if (message_search(&proto_main_msg_file, &messageListItem)) { const char* objectName = object_name(a2); char formattedText[260]; sprintf(formattedText, messageListItem.text, objectName); a3(formattedText); } } return -1; } // 0x49AD78 int obj_examine(Object* a1, Object* a2) { return obj_examine_func(a1, a2, display_print); } // Performs examine (reading description) action and passes resulting text // to given callback. // // [critter] is a critter who's performing an action. Can be NULL. // [fn] can be called up to three times when [a2] is an ammo. // // 0x49AD88 int obj_examine_func(Object* critter, Object* target, void (*fn)(char* string)) { if (critter_is_dead(critter)) { return -1; } if (FID_TYPE(target->fid) == OBJ_TYPE_TILE) { return -1; } bool scriptOverrides = false; if (target->sid != -1) { scr_set_objs(target->sid, critter, target); exec_script_proc(target->sid, SCRIPT_PROC_DESCRIPTION); Script* script; if (scr_ptr(target->sid, &script) == -1) { return -1; } scriptOverrides = script->scriptOverrides; } if (!scriptOverrides) { char* description = object_description(target); if (description != NULL && strcmp(description, proto_none_str) == 0) { description = NULL; } if (description == NULL || *description == '\0') { MessageListItem messageListItem; messageListItem.num = 493; if (!message_search(&proto_main_msg_file, &messageListItem)) { debug_printf("\nError: Can't find msg num!"); } fn(messageListItem.text); } else { if (PID_TYPE(target->pid) != OBJ_TYPE_CRITTER || !critter_is_dead(target)) { fn(description); } } } if (critter == NULL || critter != obj_dude) { return 0; } char formattedText[260]; int type = PID_TYPE(target->pid); if (type == OBJ_TYPE_CRITTER) { if (target != obj_dude && perk_level(obj_dude, PERK_AWARENESS) && !critter_is_dead(target)) { MessageListItem hpMessageListItem; if (critter_body_type(target) != BODY_TYPE_BIPED) { // It has %d/%d hps hpMessageListItem.num = 537; } else { // 535: He has %d/%d hps // 536: She has %d/%d hps hpMessageListItem.num = 535 + critterGetStat(target, STAT_GENDER); } Object* item2 = inven_right_hand(target); if (item2 != NULL && item_get_type(item2) != ITEM_TYPE_WEAPON) { item2 = NULL; } if (!message_search(&proto_main_msg_file, &hpMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } if (item2 != NULL) { MessageListItem weaponMessageListItem; if (item_w_caliber(item2) != 0) { weaponMessageListItem.num = 547; // and is wielding a %s with %d/%d shots of %s. } else { weaponMessageListItem.num = 546; // and is wielding a %s. } if (!message_search(&proto_main_msg_file, &weaponMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } char format[80]; sprintf(format, "%s%s", hpMessageListItem.text, weaponMessageListItem.text); if (item_w_caliber(item2) != 0) { const int ammoTypePid = item_w_ammo_pid(item2); const char* ammoName = proto_name(ammoTypePid); const int ammoCapacity = item_w_max_ammo(item2); const int ammoQuantity = item_w_curr_ammo(item2); const char* weaponName = object_name(item2); const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS); const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS); sprintf(formattedText, format, currentHitPoints, maxiumHitPoints, weaponName, ammoQuantity, ammoCapacity, ammoName); } else { const char* weaponName = object_name(item2); const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS); const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS); sprintf(formattedText, format, currentHitPoints, maxiumHitPoints, weaponName); } } else { MessageListItem endingMessageListItem; if (critter_is_crippled(target)) { endingMessageListItem.num = 544; // , } else { endingMessageListItem.num = 545; // . } if (!message_search(&proto_main_msg_file, &endingMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS); const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS); sprintf(formattedText, hpMessageListItem.text, currentHitPoints, maxiumHitPoints); strcat(formattedText, endingMessageListItem.text); } } else { int v12 = 0; if (critter_is_crippled(target)) { v12 -= 2; } int v16; const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS); const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS); if (currentHitPoints <= 0 || critter_is_dead(target)) { v16 = 0; } else if (currentHitPoints == maxiumHitPoints) { v16 = 4; } else { v16 = (currentHitPoints * 3) / maxiumHitPoints + 1; } MessageListItem hpMessageListItem; hpMessageListItem.num = 500 + v16; if (!message_search(&proto_main_msg_file, &hpMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } if (v16 > 4) { // Error: lookup_val out of range hpMessageListItem.num = 550; if (!message_search(&proto_main_msg_file, &hpMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } debug_printf(hpMessageListItem.text); return 0; } MessageListItem v66; if (target == obj_dude) { // You look %s v66.num = 520 + v12; if (!message_search(&proto_main_msg_file, &v66)) { debug_printf("\nError: Can't find msg num!"); exit(1); } sprintf(formattedText, v66.text, hpMessageListItem.text); } else { // %s %s v66.num = 521 + v12; if (!message_search(&proto_main_msg_file, &v66)) { debug_printf("\nError: Can't find msg num!"); exit(1); } MessageListItem v63; v63.num = 522 + critterGetStat(target, STAT_GENDER); if (!message_search(&proto_main_msg_file, &v63)) { debug_printf("\nError: Can't find msg num!"); exit(1); } sprintf(formattedText, v63.text, hpMessageListItem.text); } } if (critter_is_crippled(target)) { const int maxiumHitPoints = critterGetStat(target, STAT_MAXIMUM_HIT_POINTS); const int currentHitPoints = critterGetStat(target, STAT_CURRENT_HIT_POINTS); MessageListItem v63; v63.num = maxiumHitPoints >= currentHitPoints ? 531 : 530; if (target == obj_dude) { v63.num += 2; } if (!message_search(&proto_main_msg_file, &v63)) { debug_printf("\nError: Can't find msg num!"); exit(1); } strcat(formattedText, v63.text); } fn(formattedText); } else if (type == OBJ_TYPE_SCENERY) { if (target->pid == PROTO_ID_CAR) { MessageListItem carMessageListItem; carMessageListItem.num = 549; // The car is running at %d%% power. int car = game_get_global_var(GVAR_PLAYER_GOT_CAR); if (car == 0) { carMessageListItem.num = 548; // The car doesn't look like it's working right now. } if (!message_search(&proto_main_msg_file, &carMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } if (car != 0) { sprintf(formattedText, carMessageListItem.text, 100 * wmCarGasAmount() / 80000); } else { strcpy(formattedText, carMessageListItem.text); } fn(formattedText); } } else if (type == OBJ_TYPE_ITEM) { int itemType = item_get_type(target); if (itemType == ITEM_TYPE_WEAPON) { if (item_w_caliber(target) != 0) { MessageListItem weaponMessageListItem; weaponMessageListItem.num = 526; if (!message_search(&proto_main_msg_file, &weaponMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } int ammoTypePid = item_w_ammo_pid(target); const char* ammoName = proto_name(ammoTypePid); int ammoCapacity = item_w_max_ammo(target); int ammoQuantity = item_w_curr_ammo(target); sprintf(formattedText, weaponMessageListItem.text, ammoQuantity, ammoCapacity, ammoName); fn(formattedText); } } else if (itemType == ITEM_TYPE_AMMO) { MessageListItem ammoMessageListItem; ammoMessageListItem.num = 510; if (!message_search(&proto_main_msg_file, &ammoMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } sprintf(formattedText, ammoMessageListItem.text, item_a_ac_adjust(target)); fn(formattedText); ammoMessageListItem.num++; if (!message_search(&proto_main_msg_file, &ammoMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } sprintf(formattedText, ammoMessageListItem.text, item_a_dr_adjust(target)); fn(formattedText); ammoMessageListItem.num++; if (!message_search(&proto_main_msg_file, &ammoMessageListItem)) { debug_printf("\nError: Can't find msg num!"); exit(1); } sprintf(formattedText, ammoMessageListItem.text, item_a_dam_mult(target), item_a_dam_div(target)); fn(formattedText); } } return 0; } // 0x49B650 int obj_pickup(Object* critter, Object* item) { bool overriden = false; if (item->sid != -1) { scr_set_objs(item->sid, critter, item); exec_script_proc(item->sid, SCRIPT_PROC_PICKUP); Script* script; if (scr_ptr(item->sid, &script) == -1) { return -1; } overriden = script->scriptOverrides; } if (!overriden) { int rc; if (item->pid == PROTO_ID_MONEY) { int amount = item_caps_get_amount(item); if (amount <= 0) { amount = 1; } rc = item_add_mult(critter, item, amount); if (rc == 0) { item_caps_set_amount(item, 0); } } else { rc = item_add_mult(critter, item, 1); } if (rc == 0) { Rect rect; obj_disconnect(item, &rect); tile_refresh_rect(&rect, item->elevation); } else { MessageListItem messageListItem; // You cannot pick up that item. You are at your maximum weight capacity. messageListItem.num = 905; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } } return 0; } // 0x49B73C int obj_remove_from_inven(Object* critter, Object* item) { Rect updatedRect; int fid; int v11 = 0; if (inven_right_hand(critter) == item) { if (critter != obj_dude || intface_is_item_right_hand()) { fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, FID_ANIM_TYPE(critter->fid), 0, critter->rotation); obj_change_fid(critter, fid, &updatedRect); v11 = 2; } else { v11 = 1; } } else if (inven_left_hand(critter) == item) { if (critter == obj_dude && !intface_is_item_right_hand()) { fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, FID_ANIM_TYPE(critter->fid), 0, critter->rotation); obj_change_fid(critter, fid, &updatedRect); v11 = 2; } else { v11 = 1; } } else if (inven_worn(critter) == item) { if (critter == obj_dude) { int v5 = 1; Proto* proto; if (proto_ptr(0x1000000, &proto) != -1) { v5 = proto->fid; } fid = art_id(OBJ_TYPE_CRITTER, v5, FID_ANIM_TYPE(critter->fid), (critter->fid & 0xF000) >> 12, critter->rotation); obj_change_fid(critter, fid, &updatedRect); v11 = 3; } } int rc = item_remove_mult(critter, item, 1); if (v11 >= 2) { tile_refresh_rect(&updatedRect, critter->elevation); } if (v11 <= 2 && critter == obj_dude) { intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } return rc; } // 0x49B8B0 int obj_drop(Object* a1, Object* a2) { if (a2 == NULL) { return -1; } bool scriptOverrides = false; if (a1->sid != -1) { scr_set_objs(a1->sid, a2, NULL); exec_script_proc(a1->sid, SCRIPT_PROC_IS_DROPPING); Script* scr; if (scr_ptr(a1->sid, &scr) == -1) { return -1; } scriptOverrides = scr->scriptOverrides; } if (scriptOverrides) { return 0; } if (a2->sid != -1) { scr_set_objs(a2->sid, a1, a2); exec_script_proc(a2->sid, SCRIPT_PROC_DROP); Script* scr; if (scr_ptr(a2->sid, &scr) == -1) { return -1; } scriptOverrides = scr->scriptOverrides; } if (scriptOverrides) { return 0; } if (obj_remove_from_inven(a1, a2) == 0) { Object* owner = obj_top_environment(a1); if (owner == NULL) { owner = a1; } Rect updatedRect; obj_connect(a2, owner->tile, owner->elevation, &updatedRect); tile_refresh_rect(&updatedRect, owner->elevation); } return 0; } // 0x49B9A0 int obj_destroy(Object* obj) { if (obj == NULL) { return -1; } int elev; Object* owner = obj->owner; if (owner != NULL) { obj_remove_from_inven(owner, obj); } else { elev = obj->elevation; } queue_remove(obj); Rect rect; obj_erase_object(obj, &rect); if (owner == NULL) { tile_refresh_rect(&rect, elev); } return 0; } // Read a book. // // 0x49B9F0 static int obj_use_book(Object* book) { MessageListItem messageListItem; int messageId = -1; int skill; switch (book->pid) { case PROTO_ID_BIG_BOOK_OF_SCIENCE: // You learn new science information. messageId = 802; skill = SKILL_SCIENCE; break; case PROTO_ID_DEANS_ELECTRONICS: // You learn a lot about repairing broken electronics. messageId = 803; skill = SKILL_REPAIR; break; case PROTO_ID_FIRST_AID_BOOK: // You learn new ways to heal injury. messageId = 804; skill = SKILL_FIRST_AID; break; case PROTO_ID_SCOUT_HANDBOOK: // You learn a lot about wilderness survival. messageId = 806; skill = SKILL_OUTDOORSMAN; break; case PROTO_ID_GUNS_AND_BULLETS: // You learn how to handle your guns better. messageId = 805; skill = SKILL_SMALL_GUNS; break; } if (messageId == -1) { return -1; } if (isInCombat()) { // You cannot do that in combat. messageListItem.num = 902; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } return 0; } int increase = (100 - skill_level(obj_dude, skill)) / 10; if (increase <= 0) { messageId = 801; } else { if (perk_level(obj_dude, PERK_COMPREHENSION)) { increase = 150 * increase / 100; } for (int i = 0; i < increase; i++) { skill_inc_point_force(obj_dude, skill); } } palette_fade_to(black_palette); int intelligence = critterGetStat(obj_dude, STAT_INTELLIGENCE); inc_game_time_in_seconds(3600 * (11 - intelligence)); scr_exec_map_update_scripts(); palette_fade_to(cmap); // You read the book. messageListItem.num = 800; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } messageListItem.num = messageId; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } return 1; } // Light a flare. // // 0x49BBA8 static int obj_use_flare(Object* critter_obj, Object* flare) { MessageListItem messageListItem; if (flare->pid != PROTO_ID_FLARE) { return -1; } if ((flare->flags & OBJECT_USED) != 0) { if (critter_obj == obj_dude) { // The flare is already lit. messageListItem.num = 588; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } } else { if (critter_obj == obj_dude) { // You light the flare. messageListItem.num = 588; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } flare->pid = PROTO_ID_LIT_FLARE; obj_set_light(flare, 8, 0x10000, NULL); queue_add(72000, flare, NULL, EVENT_TYPE_FLARE); } return 0; } // 0x49BC60 int obj_use_radio(Object* item) { Script* scr; if (item->sid == -1) { return -1; } scr_set_objs(item->sid, obj_dude, item); exec_script_proc(item->sid, SCRIPT_PROC_USE); if (scr_ptr(item->sid, &scr) == -1) { return -1; } return 0; } // 0x49BCB4 static int obj_use_explosive(Object* explosive) { MessageListItem messageListItem; int pid = explosive->pid; if (pid != PROTO_ID_DYNAMITE_I && pid != PROTO_ID_PLASTIC_EXPLOSIVES_I && pid != PROTO_ID_DYNAMITE_II && pid != PROTO_ID_PLASTIC_EXPLOSIVES_II) { return -1; } if ((explosive->flags & OBJECT_USED) != 0) { // The timer is already ticking. messageListItem.num = 590; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } else { int seconds = inven_set_timer(explosive); if (seconds != -1) { // You set the timer. messageListItem.num = 589; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } if (pid == PROTO_ID_DYNAMITE_I) { explosive->pid = PROTO_ID_DYNAMITE_II; } else if (pid == PROTO_ID_PLASTIC_EXPLOSIVES_I) { explosive->pid = PROTO_ID_PLASTIC_EXPLOSIVES_II; } int delay = 10 * seconds; int roll; if (perkHasRank(obj_dude, PERK_DEMOLITION_EXPERT)) { roll = ROLL_SUCCESS; } else { roll = skill_result(obj_dude, SKILL_TRAPS, 0, NULL); } int eventType; switch (roll) { case ROLL_CRITICAL_FAILURE: delay = 0; eventType = EVENT_TYPE_EXPLOSION_FAILURE; break; case ROLL_FAILURE: eventType = EVENT_TYPE_EXPLOSION_FAILURE; delay /= 2; break; default: eventType = EVENT_TYPE_EXPLOSION; break; } queue_add(delay, explosive, NULL, eventType); } } return 2; } // Recharge car with given item // Returns -1 when car cannot be recharged with given item. // Returns 1 when car is recharged. // // 0x49BDE8 int obj_use_power_on_car(Object* item) { // 0x49A990 static MessageListItem messageListItem; int messageNum; bool isEnergy = false; int energyDensity; switch (item->pid) { case PROTO_ID_SMALL_ENERGY_CELL: energyDensity = 16000; isEnergy = true; break; case PROTO_ID_MICRO_FUSION_CELL: energyDensity = 40000; isEnergy = true; break; } if (!isEnergy) { return -1; } if (wmCarGasAmount() < CAR_FUEL_MAX) { int energy = item_w_curr_ammo(item) * energyDensity; int capacity = item_w_max_ammo(item); // NOTE: that function will never return -1 if (wmCarFillGas(energy / capacity) == -1) { return -1; } // You charge the car with more power. messageNum = 595; } else { // The car is already full of power. messageNum = 596; } char* text = getmsg(&proto_main_msg_file, &messageListItem, messageNum); display_print(text); return 1; } // 0x49BE88 static int obj_use_misc_item(Object* item) { if (item == NULL) { return -1; } switch (item->pid) { case PROTO_ID_RAMIREZ_BOX_CLOSED: case PROTO_ID_RAIDERS_MAP: case PROTO_ID_CATS_PAW_ISSUE_5: case PROTO_ID_PIP_BOY_LINGUAL_ENHANCER: case PROTO_ID_SURVEY_MAP: case PROTO_ID_PIP_BOY_MEDICAL_ENHANCER: if (item->sid == -1) { return 1; } scr_set_objs(item->sid, obj_dude, item); exec_script_proc(item->sid, SCRIPT_PROC_USE); Script* scr; if (scr_ptr(item->sid, &scr) == -1) { return -1; } return 1; } return -1; } // 0x49BF38 int protinst_use_item(Object* critter, Object* item) { int rc; MessageListItem messageListItem; switch (item_get_type(item)) { case ITEM_TYPE_DRUG: rc = -1; break; case ITEM_TYPE_WEAPON: case ITEM_TYPE_MISC: rc = obj_use_book(item); if (rc != -1) { break; } rc = obj_use_flare(critter, item); if (rc == 0) { break; } rc = obj_use_misc_item(item); if (rc != -1) { break; } rc = obj_use_radio(item); if (rc == 0) { break; } rc = obj_use_explosive(item); if (rc == 0 || rc == 2) { break; } // TODO: Not sure about these two conditions. if (item_m_uses_charges(item)) { rc = item_m_use_charged_item(critter, item); if (rc == 0) { break; } } // FALLTHROUGH default: // That does nothing messageListItem.num = 582; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } rc = -1; } return rc; } // 0x49BFE8 static int protinstTestDroppedExplosive(Object* a1) { if (a1->pid == PROTO_ID_DYNAMITE_II || a1->pid == PROTO_ID_PLASTIC_EXPLOSIVES_II) { Attack attack; combat_ctd_init(&attack, obj_dude, 0, HIT_MODE_PUNCH, HIT_LOCATION_TORSO); attack.attackerFlags = DAM_HIT; attack.tile = obj_dude->tile; compute_explosion_on_extras(&attack, 0, 0, 1); int team = obj_dude->data.critter.combat.team; Object* v2 = NULL; for (int index = 0; index < attack.extrasLength; index++) { Object* v5 = attack.extras[index]; if (v5 != obj_dude && v5->data.critter.combat.team != team && stat_result(v5, STAT_PERCEPTION, 0, NULL) >= 2) { critter_set_who_hit_me(v5, obj_dude); if (v2 == NULL) { v2 = v5; } } } if (v2 != NULL && !isInCombat()) { STRUCT_664980 attack; attack.attacker = v2; attack.defender = obj_dude; attack.actionPointsBonus = 0; attack.accuracyBonus = 0; attack.minDamage = 0; attack.maxDamage = 99999; attack.field_1C = 0; scripts_request_combat(&attack); } } return 0; } // 0x49C124 int obj_use_item(Object* a1, Object* a2) { int rc = protinst_use_item(a1, a2); if (rc == 1 || rc == 2) { Object* root = obj_top_environment(a2); if (root != NULL) { int flags = a2->flags & OBJECT_IN_ANY_HAND; item_remove_mult(root, a2, 1); Object* v8 = item_replace(root, a2, flags); if (root == obj_dude) { int leftItemAction; int rightItemAction; intface_get_item_states(&leftItemAction, &rightItemAction); if (v8 == NULL) { if ((flags & OBJECT_IN_LEFT_HAND) != 0) { leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } else if ((flags & OBJECT_IN_RIGHT_HAND) != 0) { rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } else { leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT; rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } } intface_update_items(false, leftItemAction, rightItemAction); } } if (rc == 1) { obj_destroy(a2); } else if (rc == 2 && root != NULL) { Rect updatedRect; obj_connect(a2, root->tile, root->elevation, &updatedRect); tile_refresh_rect(&updatedRect, root->elevation); protinstTestDroppedExplosive(a2); } rc = 0; } scr_exec_map_update_scripts(); return rc; } // 0x49C240 static int protinst_default_use_item(Object* a1, Object* a2, Object* item) { char formattedText[90]; MessageListItem messageListItem; int rc; switch (item_get_type(item)) { case ITEM_TYPE_DRUG: if (PID_TYPE(a2->pid) != OBJ_TYPE_CRITTER) { if (a1 == obj_dude) { // That does nothing messageListItem.num = 582; if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } return -1; } if (critter_is_dead(a2)) { // 583: To your dismay, you realize that it is already dead. // 584: As you reach down, you realize that it is already dead. // 585: Alas, you are too late. // 586: That won't work on the dead. messageListItem.num = 583 + roll_random(0, 3); if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } rc = item_d_take_drug(a2, item); if (a1 == obj_dude && a2 != obj_dude) { // TODO: Looks like there is bug in this branch, message 580 will never be shown, // as we can only be here when target is not dude. // 580: You use the %s. // 581: You use the %s on %s. messageListItem.num = 580 + (a2 != obj_dude); if (!message_search(&proto_main_msg_file, &messageListItem)) { return -1; } sprintf(formattedText, messageListItem.text, object_name(item), object_name(a2)); display_print(formattedText); } if (a2 == obj_dude) { intface_update_hit_points(true); } return rc; case ITEM_TYPE_AMMO: rc = obj_use_power_on_car(item); if (rc == 1) { return 1; } break; case ITEM_TYPE_WEAPON: case ITEM_TYPE_MISC: rc = obj_use_flare(a1, item); if (rc == 0) { return 0; } break; } messageListItem.num = 582; if (message_search(&proto_main_msg_file, &messageListItem)) { sprintf(formattedText, messageListItem.text); display_print(formattedText); } return -1; } // 0x49C3CC int protinst_use_item_on(Object* a1, Object* a2, Object* item) { int messageId = -1; int criticalChanceModifier = 0; int skill = -1; switch (item->pid) { case PROTO_ID_DOCTORS_BAG: // The supplies in the Doctor's Bag run out. messageId = 900; criticalChanceModifier = 20; skill = SKILL_DOCTOR; break; case PROTO_ID_FIRST_AID_KIT: // The supplies in the First Aid Kit run out. messageId = 901; criticalChanceModifier = 20; skill = SKILL_FIRST_AID; break; case PROTO_ID_PARAMEDICS_BAG: // The supplies in the Paramedic's Bag run out. messageId = 910; criticalChanceModifier = 40; skill = SKILL_DOCTOR; break; case PROTO_ID_FIELD_MEDIC_FIRST_AID_KIT: // The supplies in the Field Medic First Aid Kit run out. messageId = 911; criticalChanceModifier = 40; skill = SKILL_FIRST_AID; break; } if (skill == -1) { Script* script; if (item->sid == -1) { if (a2->sid == -1) { return protinst_default_use_item(a1, a2, item); } scr_set_objs(a2->sid, a1, item); exec_script_proc(a2->sid, SCRIPT_PROC_USE_OBJ_ON); if (scr_ptr(a2->sid, &script) == -1) { return -1; } if (!script->scriptOverrides) { return protinst_default_use_item(a1, a2, item); } } else { scr_set_objs(item->sid, a1, a2); exec_script_proc(item->sid, SCRIPT_PROC_USE_OBJ_ON); if (scr_ptr(item->sid, &script) == -1) { return -1; } if (script->field_28 == 0) { if (a2->sid == -1) { return protinst_default_use_item(a1, a2, item); } scr_set_objs(a2->sid, a1, item); exec_script_proc(a2->sid, SCRIPT_PROC_USE_OBJ_ON); Script* script; if (scr_ptr(a2->sid, &script) == -1) { return -1; } if (!script->scriptOverrides) { return protinst_default_use_item(a1, a2, item); } } } return script->field_28; } if (isInCombat()) { MessageListItem messageListItem; // You cannot do that in combat. messageListItem.num = 902; if (a1 == obj_dude) { if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } return -1; } if (skill_use(a1, a2, skill, criticalChanceModifier) != 0) { return 0; } if (roll_random(1, 10) != 1) { return 0; } MessageListItem messageListItem; messageListItem.num = messageId; if (a1 == obj_dude) { if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } return 1; } // 0x49C5FC int obj_use_item_on(Object* a1, Object* a2, Object* a3) { int rc = protinst_use_item_on(a1, a2, a3); if (rc == 1) { if (a1 != NULL) { int flags = a3->flags & OBJECT_IN_ANY_HAND; item_remove_mult(a1, a3, 1); Object* v7 = item_replace(a1, a3, flags); int leftItemAction; int rightItemAction; if (a1 == obj_dude) { intface_get_item_states(&leftItemAction, &rightItemAction); } if (v7 == NULL) { if ((flags & OBJECT_IN_LEFT_HAND) != 0) { leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } else if ((flags & OBJECT_IN_RIGHT_HAND) != 0) { rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } else { leftItemAction = INTERFACE_ITEM_ACTION_DEFAULT; rightItemAction = INTERFACE_ITEM_ACTION_DEFAULT; } } intface_update_items(false, leftItemAction, rightItemAction); } obj_destroy(a3); rc = 0; } scr_exec_map_update_scripts(); return rc; } // 0x49C6BC int check_scenery_ap_cost(Object* obj, Object* a2) { if (!isInCombat()) { return 0; } int actionPoints = obj->data.critter.combat.ap; if (actionPoints >= 3) { obj->data.critter.combat.ap = actionPoints - 3; if (obj == obj_dude) { intface_update_move_points(obj_dude->data.critter.combat.ap, combat_free_move); } return 0; } MessageListItem messageListItem; // You don't have enough action points. messageListItem.num = 700; if (obj == obj_dude) { if (message_search(&proto_main_msg_file, &messageListItem)) { display_print(messageListItem.text); } } return -1; } // 0x49C740 int obj_use(Object* a1, Object* a2) { int type = FID_TYPE(a2->fid); if (a1 == obj_dude) { if (type != OBJ_TYPE_SCENERY) { return -1; } } else { if (type != OBJ_TYPE_SCENERY) { return 0; } } Proto* sceneryProto; if (proto_ptr(a2->pid, &sceneryProto) == -1) { return -1; } if (PID_TYPE(a2->pid) == OBJ_TYPE_SCENERY && sceneryProto->scenery.type == SCENERY_TYPE_DOOR) { return obj_use_door(a1, a2, 0); } bool scriptOverrides = false; if (a2->sid != -1) { scr_set_objs(a2->sid, a1, a2); exec_script_proc(a2->sid, SCRIPT_PROC_USE); Script* script; if (scr_ptr(a2->sid, &script) == -1) { return -1; } scriptOverrides = script->scriptOverrides; } if (!scriptOverrides) { if (PID_TYPE(a2->pid) == OBJ_TYPE_SCENERY) { if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_DOWN) { if (obj_use_ladder_top(a1, a2, 0) == 0) { scriptOverrides = true; } } else if (sceneryProto->scenery.type == SCENERY_TYPE_LADDER_UP) { if (obj_use_ladder_bottom(a1, a2, 0) == 0) { scriptOverrides = true; } } else if (sceneryProto->scenery.type == SCENERY_TYPE_STAIRS) { if (obj_use_stairs(a1, a2, 0) == 0) { scriptOverrides = true; } } } } if (!scriptOverrides) { if (a1 == obj_dude) { // You see: %s MessageListItem messageListItem; messageListItem.num = 480; if (!message_search(&proto_main_msg_file, &messageListItem)) { return -1; } char formattedText[260]; const char* name = object_name(a2); sprintf(formattedText, messageListItem.text, name); display_print(formattedText); } } scr_exec_map_update_scripts(); return 0; } // 0x49C900 int obj_use_ladder_top(Object* a1, Object* ladder, int a3) { int builtTile = ladder->data.scenery.ladder.destinationBuiltTile; if (builtTile == -1) { return -1; } int tile = builtTileGetTile(builtTile); int elevation = builtTileGetElevation(builtTile); if (ladder->data.scenery.ladder.destinationMap != 0) { MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = ladder->data.scenery.ladder.destinationMap; transition.elevation = elevation; transition.tile = tile; transition.rotation = builtTileGetRotation(builtTile); map_leave_map(&transition); wmMapMarkMapEntranceState(transition.map, elevation, 1); } else { Rect updatedRect; if (obj_move_to_tile(a1, tile, elevation, &updatedRect) == -1) { return -1; } tile_refresh_rect(&updatedRect, map_elevation); } return 0; } // 0x49C9A4 int obj_use_ladder_bottom(Object* a1, Object* ladder, int a3) { int builtTile = ladder->data.scenery.ladder.destinationBuiltTile; if (builtTile == -1) { return -1; } int tile = builtTileGetTile(builtTile); int elevation = builtTileGetElevation(builtTile); if (ladder->data.scenery.ladder.destinationMap != 0) { MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = ladder->data.scenery.ladder.destinationMap; transition.elevation = elevation; transition.tile = tile; transition.rotation = builtTileGetRotation(builtTile); map_leave_map(&transition); wmMapMarkMapEntranceState(transition.map, elevation, 1); } else { Rect updatedRect; if (obj_move_to_tile(a1, tile, elevation, &updatedRect) == -1) { return -1; } tile_refresh_rect(&updatedRect, map_elevation); } return 0; } // 0x49CA48 int obj_use_stairs(Object* a1, Object* stairs, int a3) { int builtTile = stairs->data.scenery.stairs.destinationBuiltTile; if (builtTile == -1) { return -1; } int tile = builtTileGetTile(builtTile); int elevation = builtTileGetElevation(builtTile); if (stairs->data.scenery.stairs.destinationMap > 0) { MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = stairs->data.scenery.stairs.destinationMap; transition.elevation = elevation; transition.tile = tile; transition.rotation = builtTileGetRotation(builtTile); map_leave_map(&transition); wmMapMarkMapEntranceState(transition.map, elevation, 1); } else { Rect updatedRect; if (obj_move_to_tile(a1, tile, elevation, &updatedRect) == -1) { return -1; } tile_refresh_rect(&updatedRect, map_elevation); } return 0; } // 0x49CAF4 static int set_door_state_open(Object* a1, Object* a2) { a1->data.scenery.door.openFlags |= 0x01; return 0; } // 0x49CB04 static int set_door_state_closed(Object* a1, Object* a2) { a1->data.scenery.door.openFlags &= ~0x01; return 0; } // 0x49CB14 static int check_door_state(Object* a1, Object* a2) { if ((a1->data.scenery.door.openFlags & 0x01) == 0) { a1->flags &= ~OBJECT_OPEN_DOOR; obj_rebuild_all_light(); tile_refresh_display(); if (a1->frame == 0) { return 0; } CacheEntry* artHandle; Art* art = art_ptr_lock(a1->fid, &artHandle); if (art == NULL) { return -1; } Rect dirty; Rect temp; obj_bound(a1, &dirty); for (int frame = a1->frame - 1; frame >= 0; frame--) { int x; int y; art_frame_hot(art, frame, a1->rotation, &x, &y); obj_offset(a1, -x, -y, &temp); } obj_set_frame(a1, 0, &temp); rect_min_bound(&dirty, &temp, &dirty); tile_refresh_rect(&dirty, map_elevation); art_ptr_unlock(artHandle); return 0; } else { a1->flags |= OBJECT_OPEN_DOOR; obj_rebuild_all_light(); tile_refresh_display(); CacheEntry* artHandle; Art* art = art_ptr_lock(a1->fid, &artHandle); if (art == NULL) { return -1; } int frameCount = art_frame_max_frame(art); if (a1->frame == frameCount - 1) { art_ptr_unlock(artHandle); return 0; } Rect dirty; Rect temp; obj_bound(a1, &dirty); for (int frame = a1->frame + 1; frame < frameCount; frame++) { int x; int y; art_frame_hot(art, frame, a1->rotation, &x, &y); obj_offset(a1, x, y, &temp); } obj_set_frame(a1, frameCount - 1, &temp); rect_min_bound(&dirty, &temp, &dirty); tile_refresh_rect(&dirty, map_elevation); art_ptr_unlock(artHandle); return 0; } } // 0x49CCB8 int obj_use_door(Object* a1, Object* a2, int a3) { if (obj_is_locked(a2)) { const char* sfx = gsnd_build_open_sfx_name(a2, SCENERY_SOUND_EFFECT_LOCKED); gsound_play_sfx_file(sfx); } bool scriptOverrides = false; if (a2->sid != -1) { scr_set_objs(a2->sid, a1, a2); exec_script_proc(a2->sid, SCRIPT_PROC_USE); Script* script; if (scr_ptr(a2->sid, &script) == -1) { return -1; } scriptOverrides = script->scriptOverrides; } if (!scriptOverrides) { int start; int end; int step; if (a2->frame != 0) { if (obj_blocking_at(NULL, a2->tile, a2->elevation) != 0) { MessageListItem messageListItem; char* text = getmsg(&proto_main_msg_file, &messageListItem, 597); display_print(text); return -1; } start = 1; end = (a3 == 0) - 1; step = -1; } else { if (a2->data.scenery.door.openFlags & 0x01) { return -1; } start = 0; end = (a3 != 0) + 1; step = 1; } register_begin(ANIMATION_REQUEST_RESERVED); for (int i = start; i != end; i += step) { if (i != 0) { if (a3 == 0) { register_object_call(a2, a2, set_door_state_closed, -1); } const char* sfx = gsnd_build_open_sfx_name(a2, SCENERY_SOUND_EFFECT_CLOSED); register_object_play_sfx(a2, sfx, -1); register_object_animate_reverse(a2, ANIM_STAND, 0); } else { if (a3 == 0) { register_object_call(a2, a2, set_door_state_open, -1); } const char* sfx = gsnd_build_open_sfx_name(a2, SCENERY_SOUND_EFFECT_CLOSED); register_object_play_sfx(a2, sfx, -1); register_object_animate(a2, ANIM_STAND, 0); } } register_object_must_call(a2, a2, check_door_state, -1); register_end(); } return 0; } // 0x49CE7C int obj_use_container(Object* critter, Object* item) { if (FID_TYPE(item->fid) != OBJ_TYPE_ITEM) { return -1; } Proto* itemProto; if (proto_ptr(item->pid, &itemProto) == -1) { return -1; } if (itemProto->item.type != ITEM_TYPE_CONTAINER) { return -1; } if (obj_is_locked(item)) { const char* sfx = gsnd_build_open_sfx_name(item, SCENERY_SOUND_EFFECT_LOCKED); gsound_play_sfx_file(sfx); if (critter == obj_dude) { MessageListItem messageListItem; // It is locked. messageListItem.num = 487; if (!message_search(&proto_main_msg_file, &messageListItem)) { return -1; } display_print(messageListItem.text); } return -1; } bool overriden = false; if (item->sid != -1) { scr_set_objs(item->sid, critter, item); exec_script_proc(item->sid, SCRIPT_PROC_USE); Script* script; if (scr_ptr(item->sid, &script) == -1) { return -1; } overriden = script->scriptOverrides; } if (overriden) { return -1; } register_begin(ANIMATION_REQUEST_RESERVED); if (item->frame == 0) { const char* sfx = gsnd_build_open_sfx_name(item, SCENERY_SOUND_EFFECT_OPEN); register_object_play_sfx(item, sfx, 0); register_object_animate(item, ANIM_STAND, 0); } else { const char* sfx = gsnd_build_open_sfx_name(item, SCENERY_SOUND_EFFECT_CLOSED); register_object_play_sfx(item, sfx, 0); register_object_animate_reverse(item, ANIM_STAND, 0); } register_end(); if (critter == obj_dude) { MessageListItem messageListItem; messageListItem.num = item->frame != 0 ? 486 // You search the %s. : 485; // You close the %s. if (!message_search(&proto_main_msg_file, &messageListItem)) { return -1; } char formattedText[260]; const char* objectName = object_name(item); sprintf(formattedText, messageListItem.text, objectName); display_print(formattedText); } return 0; } // 0x49D078 int obj_use_skill_on(Object* source, Object* target, int skill) { if (obj_lock_is_jammed(target)) { if (source == obj_dude) { MessageListItem messageListItem; messageListItem.num = 2001; if (message_search(&misc_message_file, &messageListItem)) { display_print(messageListItem.text); } } return -1; } Proto* proto; if (proto_ptr(target->pid, &proto) == -1) { return -1; } bool scriptOverrides = false; if (target->sid != -1) { scr_set_objs(target->sid, source, target); scr_set_action_num(target->sid, skill); exec_script_proc(target->sid, SCRIPT_PROC_USE_SKILL_ON); Script* script; if (scr_ptr(target->sid, &script) == -1) { return -1; } scriptOverrides = script->scriptOverrides; } if (!scriptOverrides) { skill_use(source, target, skill, 0); } return 0; } // NOTE: Unused. // // 0x49D140 bool obj_is_a_portal(Object* obj) { if (obj == NULL) { return false; } Proto* proto; if (proto_ptr(obj->pid, &proto) == -1) { return false; } return proto->scenery.type == SCENERY_TYPE_DOOR; } // 0x49D178 bool obj_is_lockable(Object* obj) { Proto* proto; if (obj == NULL) { return false; } if (proto_ptr(obj->pid, &proto) == -1) { return false; } switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: if (proto->item.type == ITEM_TYPE_CONTAINER) { return true; } break; case OBJ_TYPE_SCENERY: if (proto->scenery.type == SCENERY_TYPE_DOOR) { return true; } break; } return false; } // 0x49D1C8 bool obj_is_locked(Object* obj) { if (obj == NULL) { return false; } ObjectData* data = &(obj->data); switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: return data->flags & CONTAINER_FLAG_LOCKED; case OBJ_TYPE_SCENERY: return data->scenery.door.openFlags & DOOR_FLAG_LOCKED; } return false; } // 0x49D20C int obj_lock(Object* object) { if (object == NULL) { return -1; } switch (PID_TYPE(object->pid)) { case OBJ_TYPE_ITEM: object->data.flags |= OBJ_LOCKED; break; case OBJ_TYPE_SCENERY: object->data.scenery.door.openFlags |= OBJ_LOCKED; break; default: return -1; } return 0; } // 0x49D250 int obj_unlock(Object* object) { if (object == NULL) { return -1; } switch (PID_TYPE(object->pid)) { case OBJ_TYPE_ITEM: object->data.flags &= ~OBJ_LOCKED; return 0; case OBJ_TYPE_SCENERY: object->data.scenery.door.openFlags &= ~OBJ_LOCKED; return 0; } return -1; } // 0x49D294 bool obj_is_openable(Object* obj) { Proto* proto; if (obj == NULL) { return false; } if (proto_ptr(obj->pid, &proto) == -1) { return false; } switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: if (proto->item.type == ITEM_TYPE_CONTAINER) { return true; } break; case OBJ_TYPE_SCENERY: if (proto->scenery.type == SCENERY_TYPE_DOOR) { return true; } break; } return false; } // 0x49D2E4 int obj_is_open(Object* object) { return object->frame != 0; } // 0x49D2F4 int obj_toggle_open(Object* obj) { if (obj == NULL) { return -1; } if (!obj_is_openable(obj)) { return -1; } if (obj_is_locked(obj)) { return -1; } obj_unjam_lock(obj); register_begin(ANIMATION_REQUEST_RESERVED); if (obj->frame != 0) { register_object_must_call(obj, obj, set_door_state_closed, -1); const char* sfx = gsnd_build_open_sfx_name(obj, SCENERY_SOUND_EFFECT_CLOSED); register_object_play_sfx(obj, sfx, -1); register_object_animate_reverse(obj, ANIM_STAND, 0); } else { register_object_must_call(obj, obj, set_door_state_open, -1); const char* sfx = gsnd_build_open_sfx_name(obj, SCENERY_SOUND_EFFECT_OPEN); register_object_play_sfx(obj, sfx, -1); register_object_animate(obj, ANIM_STAND, 0); } register_object_must_call(obj, obj, check_door_state, -1); register_end(); return 0; } // 0x49D3D8 int obj_open(Object* obj) { if (obj->frame == 0) { obj_toggle_open(obj); } return 0; } // 0x49D3F4 int obj_close(Object* obj) { if (obj->frame != 0) { obj_toggle_open(obj); } return 0; } // 0x49D410 bool obj_lock_is_jammed(Object* obj) { if (!obj_is_lockable(obj)) { return false; } if (PID_TYPE(obj->pid) == OBJ_TYPE_SCENERY) { if ((obj->data.scenery.door.openFlags & OBJ_JAMMED) != 0) { return true; } } else { if ((obj->data.flags & OBJ_JAMMED) != 0) { return true; } } return false; } // jam_lock // 0x49D448 int obj_jam_lock(Object* obj) { if (!obj_is_lockable(obj)) { return -1; } ObjectData* data = &(obj->data); switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: data->flags |= CONTAINER_FLAG_JAMMED; break; case OBJ_TYPE_SCENERY: data->scenery.door.openFlags |= DOOR_FLAG_JAMMGED; break; } return 0; } // 0x49D480 int obj_unjam_lock(Object* obj) { if (!obj_is_lockable(obj)) { return -1; } ObjectData* data = &(obj->data); switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: data->flags &= ~CONTAINER_FLAG_JAMMED; break; case OBJ_TYPE_SCENERY: data->scenery.door.openFlags &= ~DOOR_FLAG_JAMMGED; break; } return 0; } // 0x49D4B8 int obj_unjam_all_locks() { Object* obj = obj_find_first(); while (obj != NULL) { obj_unjam_lock(obj); obj = obj_find_next(); } return 0; } // critter_attempt_placement // 0x49D4D4 int obj_attempt_placement(Object* obj, int tile, int elevation, int a4) { if (tile == -1) { return -1; } int newTile = tile; if (obj_blocking_at(NULL, tile, elevation) != NULL) { int v6 = a4; if (a4 < 1) { v6 = 1; } int attempts = 0; while (v6 < 7) { attempts++; if (attempts >= 100) { break; } for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { newTile = tile_num_in_direction(tile, rotation, v6); if (obj_blocking_at(NULL, newTile, elevation) == NULL && v6 > 1 && make_path(obj_dude, obj_dude->tile, newTile, NULL, 0) != 0) { break; } } v6++; } if (a4 != 1 && v6 > a4 + 2) { for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) { int candidate = tile_num_in_direction(tile, rotation, 1); if (obj_blocking_at(NULL, candidate, elevation) == NULL) { newTile = candidate; break; } } } } Rect updatedRect; obj_turn_on(obj, &updatedRect); Rect temp; if (obj_move_to_tile(obj, newTile, elevation, &temp) != -1) { rect_min_bound(&updatedRect, &temp, &updatedRect); if (elevation == map_elevation) { tile_refresh_rect(&updatedRect, elevation); } } return 0; } // 0x49D628 int objPMAttemptPlacement(Object* obj, int tile, int elevation) { if (obj == NULL) { return -1; } if (tile == -1) { return -1; } int v9 = tile; int v7 = 0; if (!wmEvalTileNumForPlacement(tile)) { v9 = obj_dude->tile; for (int v4 = 1; v4 <= 100; v4++) { // TODO: Check. v7++; v9 = tile_num_in_direction(v9, v7 % ROTATION_COUNT, 1); if (wmEvalTileNumForPlacement(v9) != 0) { break; } if (tile_dist(obj_dude->tile, v9) > 8) { v9 = tile; break; } } } obj_turn_on(obj, NULL); obj_move_to_tile(obj, v9, elevation, NULL); return 0; } ================================================ FILE: src/game/protinst.h ================================================ #ifndef FALLOUT_GAME_PROTINST_H_ #define FALLOUT_GAME_PROTINST_H_ #include #include "game/object_types.h" int obj_sid(Object* object, int* sidPtr); int obj_new_sid(Object* object, int* sidPtr); int obj_new_sid_inst(Object* obj, int a2, int a3); int obj_look_at(Object* a1, Object* a2); int obj_look_at_func(Object* a1, Object* a2, void (*a3)(char* string)); int obj_examine(Object* a1, Object* a2); int obj_examine_func(Object* critter, Object* target, void (*fn)(char* string)); int obj_pickup(Object* critter, Object* item); int obj_remove_from_inven(Object* critter, Object* item); int obj_drop(Object* a1, Object* a2); int obj_destroy(Object* obj); int obj_use_radio(Object* item_obj); int obj_use_power_on_car(Object* ammo); int protinst_use_item(Object* a1, Object* a2); int obj_use_item(Object* a1, Object* a2); int protinst_use_item_on(Object* a1, Object* a2, Object* item); int obj_use_item_on(Object* a1, Object* a2, Object* a3); int check_scenery_ap_cost(Object* obj, Object* a2); int obj_use(Object* a1, Object* a2); int obj_use_ladder_top(Object* a1, Object* ladder, int a3); int obj_use_ladder_bottom(Object* a1, Object* ladder, int a3); int obj_use_stairs(Object* a1, Object* stairs, int a3); int obj_use_door(Object* a1, Object* a2, int a3); int obj_use_container(Object* critter, Object* item); int obj_use_skill_on(Object* a1, Object* a2, int skill); bool obj_is_a_portal(Object* obj); bool obj_is_lockable(Object* obj); bool obj_is_locked(Object* obj); int obj_lock(Object* obj); int obj_unlock(Object* obj); bool obj_is_openable(Object* obj); int obj_is_open(Object* obj); int obj_toggle_open(Object* obj); int obj_open(Object* obj); int obj_close(Object* obj); bool obj_lock_is_jammed(Object* obj); int obj_jam_lock(Object* obj); int obj_unjam_lock(Object* obj); int obj_unjam_all_locks(); int obj_attempt_placement(Object* obj, int tile, int elevation, int a4); int objPMAttemptPlacement(Object* obj, int tile, int elevation); #endif /* FALLOUT_GAME_PROTINST_H_ */ ================================================ FILE: src/game/proto.c ================================================ #include "game/proto.h" #include #include #include #define WIN32_LEAN_AND_MEAN #include #include "game/art.h" #include "game/editor.h" #include "game/combat.h" #include "game/config.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "int/dialog.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmovie.h" #include "game/intface.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/perk.h" #include "game/skill.h" #include "game/stat.h" #include "game/trait.h" static char* proto_get_msg_info(int pid, int message); static int proto_read_CombatData(CritterCombatData* data, File* stream); static int proto_write_CombatData(CritterCombatData* data, File* stream); static int proto_read_item_data(ItemProtoData* item_data, int type, File* stream); static int proto_read_scenery_data(SceneryProtoData* scenery_data, int type, File* stream); static int proto_read_protoSubNode(Proto* buf, File* stream); static int proto_write_item_data(ItemProtoData* item_data, int type, File* stream); static int proto_write_scenery_data(SceneryProtoData* scenery_data, int type, File* stream); static int proto_write_protoSubNode(Proto* buf, File* stream); static void proto_remove_some_list(int type); static void proto_remove_list(int type); static int proto_new_id(int a1); // 0x51C18C char cd_path_base[MAX_PATH]; // 0x51C290 static ProtoList protolists[11] = { { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 1 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, }; // 0x51C340 static const size_t proto_sizes[11] = { sizeof(ItemProto), // 0x84 sizeof(CritterProto), // 0x1A0 sizeof(SceneryProto), // 0x38 sizeof(WallProto), // 0x24 sizeof(TileProto), // 0x1C sizeof(MiscProto), // 0x1C 0, 0, 0, 0, 0, }; // 0x51C36C static int protos_been_initialized = 0; // obj_dude_proto // 0x51C370 static CritterProto pc_proto = { 0x1000000, -1, 0x1000001, 0, 0, 0x20000000, 0, -1, 0, { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 18, 0, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 23, 0 }, { 0 }, { 0 }, 0, 0, 0, 0, -1, 0, 0, }; // 0x51C534 char proto_path_base[] = "proto\\"; // 0x664530 char* mp_perk_code_strs[1 + PERK_COUNT]; // 0x66470C char* mp_critter_stats_list[2 + STAT_COUNT]; // Message list by object type // 0 - pro_item.msg // 1 - pro_crit.msg // 2 - pro_scen.msg // 3 - pro_wall.msg // 4 - pro_tile.msg // 5 - pro_misc.msg // // 0x6647AC MessageList proto_msg_files[6]; // 0x6647DC char* race_type_strs[RACE_TYPE_COUNT]; // 0x6647E4 char* scenery_pro_type[SCENERY_TYPE_COUNT]; // proto.msg // // 0x6647FC MessageList proto_main_msg_file; // 0x664804 char* item_pro_material[MATERIAL_TYPE_COUNT]; // "" from proto.msg // // 0x664824 char* proto_none_str; // 0x664828 char* body_type_strs[BODY_TYPE_COUNT]; // 0x664834 char* item_pro_type[ITEM_TYPE_COUNT]; // 0x66484C char* damage_code_strs[DAMAGE_TYPE_COUNT]; // 0x66486C char* cal_type_strs[CALIBER_TYPE_COUNT]; // Perk names. // // 0x6648B8 char** perk_code_strs; // Stat names. // // 0x6648BC char** critter_stats_list; // NOTE: Inlined. // // 0x49E270 void proto_make_path(char* path, int pid) { strcpy(path, cd_path_base); strcat(path, proto_path_base); if (pid != -1) { strcat(path, art_dir(PID_TYPE(pid))); } } // Append proto file name to proto_path from proto.lst. // // 0x49E758 int proto_list_str(int pid, char* proto_path) { if (pid == -1) { return -1; } if (proto_path == NULL) { return -1; } char path[MAX_PATH]; proto_make_path(path, pid); strcat(path, "\\"); strcat(path, art_dir(PID_TYPE(pid))); strcat(path, ".lst"); File* stream = db_fopen(path, "rt"); int i = 1; char string[256]; while (db_fgets(string, sizeof(string), stream)) { if (i == (pid & 0xFFFFFF)) { break; } i++; } db_fclose(stream); if (i != (pid & 0xFFFFFF)) { return -1; } char* pch = strchr(string, ' '); if (pch != NULL) { *pch = '\0'; } pch = strchr(string, '\n'); if (pch != NULL) { *pch = '\0'; } strcpy(proto_path, string); return 0; } // 0x49E99C bool proto_action_can_use(int pid) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return false; } if ((proto->item.extendedFlags & 0x0800) != 0) { return true; } if (PID_TYPE(pid) == OBJ_TYPE_ITEM && proto->item.type == ITEM_TYPE_CONTAINER) { return true; } return false; } // 0x49E9DC bool proto_action_can_use_on(int pid) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return false; } if ((proto->item.extendedFlags & 0x1000) != 0) { return true; } if (PID_TYPE(pid) == OBJ_TYPE_ITEM && proto->item.type == ITEM_TYPE_DRUG) { return true; } return false; } // 0x49EA24 bool proto_action_can_talk_to(int pid) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return false; } if (PID_TYPE(pid) == OBJ_TYPE_CRITTER) { return true; } if (proto->critter.extendedFlags & 0x4000) { return true; } return false; } // Likely returns true if item with given pid can be picked up. // // 0x49EA5C int proto_action_can_pickup(int pid) { if (PID_TYPE(pid) != OBJ_TYPE_ITEM) { return false; } Proto* proto; if (proto_ptr(pid, &proto) == -1) { return false; } if (proto->item.type == ITEM_TYPE_CONTAINER) { return (proto->item.extendedFlags & 0x8000) != 0; } return true; } // 0x49EAA4 static char* proto_get_msg_info(int pid, int message) { char* v1 = proto_none_str; Proto* proto; if (proto_ptr(pid, &proto) != -1) { if (proto->messageId != -1) { MessageList* messageList = &(proto_msg_files[PID_TYPE(pid)]); MessageListItem messageListItem; messageListItem.num = proto->messageId + message; if (message_search(messageList, &messageListItem)) { v1 = messageListItem.text; } } } return v1; } // 0x49EAFC char* proto_name(int pid) { if (pid == 0x1000000) { return critter_name(obj_dude); } return proto_get_msg_info(pid, PROTOTYPE_MESSAGE_NAME); } // 0x49EB1C char* proto_description(int pid) { return proto_get_msg_info(pid, PROTOTYPE_MESSAGE_DESCRIPTION); } // 0x49EDB4 int proto_critter_init(Proto* a1, int a2) { if (!protos_been_initialized) { return -1; } int v1 = a2 & 0xFFFFFF; a1->pid = -1; a1->messageId = 100 * v1; a1->fid = art_id(OBJ_TYPE_CRITTER, v1 - 1, 0, 0, 0); a1->critter.lightDistance = 0; a1->critter.lightIntensity = 0; a1->critter.flags = 0x20000000; a1->critter.extendedFlags = 0x6000; a1->critter.sid = -1; a1->critter.data.flags = 0; a1->critter.data.bodyType = 0; a1->critter.headFid = -1; a1->critter.aiPacket = 1; if (!art_exists(a1->fid)) { a1->fid = art_id(OBJ_TYPE_CRITTER, 0, 0, 0, 0); } CritterProtoData* data = &(a1->critter.data); data->experience = 60; data->killType = 0; data->damageType = 0; stat_set_defaults(data); skill_set_defaults(data); return 0; } // 0x49EEA4 void clear_pupdate_data(Object* obj) { // NOTE: Original code is slightly different. It uses loop to zero object // data byte by byte. memset(&(obj->data), 0, sizeof(obj->data)); } // 0x49EEB8 static int proto_read_CombatData(CritterCombatData* data, File* stream) { if (db_freadInt(stream, &(data->damageLastTurn)) == -1) return -1; if (db_freadInt(stream, &(data->maneuver)) == -1) return -1; if (db_freadInt(stream, &(data->ap)) == -1) return -1; if (db_freadInt(stream, &(data->results)) == -1) return -1; if (db_freadInt(stream, &(data->aiPacket)) == -1) return -1; if (db_freadInt(stream, &(data->team)) == -1) return -1; if (db_freadInt(stream, &(data->whoHitMeCid)) == -1) return -1; return 0; } // 0x49EF40 static int proto_write_CombatData(CritterCombatData* data, File* stream) { if (db_fwriteInt(stream, data->damageLastTurn) == -1) return -1; if (db_fwriteInt(stream, data->maneuver) == -1) return -1; if (db_fwriteInt(stream, data->ap) == -1) return -1; if (db_fwriteInt(stream, data->results) == -1) return -1; if (db_fwriteInt(stream, data->aiPacket) == -1) return -1; if (db_fwriteInt(stream, data->team) == -1) return -1; if (db_fwriteInt(stream, data->whoHitMeCid) == -1) return -1; return 0; } // 0x49F004 int proto_read_protoUpdateData(Object* obj, File* stream) { Proto* proto; Inventory* inventory = &(obj->data.inventory); if (db_freadInt(stream, &(inventory->length)) == -1) return -1; if (db_freadInt(stream, &(inventory->capacity)) == -1) return -1; // TODO: See below. if (db_freadInt(stream, (int*)&(inventory->items)) == -1) return -1; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { if (db_freadInt(stream, &(obj->data.critter.field_0)) == -1) return -1; if (proto_read_CombatData(&(obj->data.critter.combat), stream) == -1) return -1; if (db_freadInt(stream, &(obj->data.critter.hp)) == -1) return -1; if (db_freadInt(stream, &(obj->data.critter.radiation)) == -1) return -1; if (db_freadInt(stream, &(obj->data.critter.poison)) == -1) return -1; } else { if (db_freadInt(stream, &(obj->data.flags)) == -1) return -1; if (obj->data.flags == 0xCCCCCCCC) { debug_printf("\nNote: Reading pud: updated_flags was un-Set!"); obj->data.flags = 0; } switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: if (proto_ptr(obj->pid, &proto) == -1) return -1; switch (proto->item.type) { case ITEM_TYPE_WEAPON: if (db_freadInt(stream, &(obj->data.item.weapon.ammoQuantity)) == -1) return -1; if (db_freadInt(stream, &(obj->data.item.weapon.ammoTypePid)) == -1) return -1; break; case ITEM_TYPE_AMMO: if (db_freadInt(stream, &(obj->data.item.ammo.quantity)) == -1) return -1; break; case ITEM_TYPE_MISC: if (db_freadInt(stream, &(obj->data.item.misc.charges)) == -1) return -1; break; case ITEM_TYPE_KEY: if (db_freadInt(stream, &(obj->data.item.key.keyCode)) == -1) return -1; break; default: break; } break; case OBJ_TYPE_SCENERY: if (proto_ptr(obj->pid, &proto) == -1) return -1; switch (proto->scenery.type) { case SCENERY_TYPE_DOOR: if (db_freadInt(stream, &(obj->data.scenery.door.openFlags)) == -1) return -1; break; case SCENERY_TYPE_STAIRS: if (db_freadInt(stream, &(obj->data.scenery.stairs.destinationBuiltTile)) == -1) return -1; if (db_freadInt(stream, &(obj->data.scenery.stairs.destinationMap)) == -1) return -1; break; case SCENERY_TYPE_ELEVATOR: if (db_freadInt(stream, &(obj->data.scenery.elevator.type)) == -1) return -1; if (db_freadInt(stream, &(obj->data.scenery.elevator.level)) == -1) return -1; break; case SCENERY_TYPE_LADDER_UP: if (map_data.version == 19) { if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1; } else { if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationMap)) == -1) return -1; if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1; } break; case SCENERY_TYPE_LADDER_DOWN: if (map_data.version == 19) { if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1; } else { if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationMap)) == -1) return -1; if (db_freadInt(stream, &(obj->data.scenery.ladder.destinationBuiltTile)) == -1) return -1; } break; } break; case OBJ_TYPE_MISC: if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) { if (db_freadInt(stream, &(obj->data.misc.map)) == -1) return -1; if (db_freadInt(stream, &(obj->data.misc.tile)) == -1) return -1; if (db_freadInt(stream, &(obj->data.misc.elevation)) == -1) return -1; if (db_freadInt(stream, &(obj->data.misc.rotation)) == -1) return -1; } break; } } return 0; } // 0x49F428 int proto_write_protoUpdateData(Object* obj, File* stream) { Proto* proto; ObjectData* data = &(obj->data); if (db_fwriteInt(stream, data->inventory.length) == -1) return -1; if (db_fwriteInt(stream, data->inventory.capacity) == -1) return -1; // TODO: Why do we need to write address of pointer? That probably means // this field is shared with something else. if (db_fwriteInt(stream, (intptr_t)data->inventory.items) == -1) return -1; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { if (db_fwriteInt(stream, data->flags) == -1) return -1; if (proto_write_CombatData(&(obj->data.critter.combat), stream) == -1) return -1; if (db_fwriteInt(stream, data->critter.hp) == -1) return -1; if (db_fwriteInt(stream, data->critter.radiation) == -1) return -1; if (db_fwriteInt(stream, data->critter.poison) == -1) return -1; } else { if (db_fwriteInt(stream, data->flags) == -1) return -1; switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: if (proto_ptr(obj->pid, &proto) == -1) return -1; switch (proto->item.type) { case ITEM_TYPE_WEAPON: if (db_fwriteInt(stream, data->item.weapon.ammoQuantity) == -1) return -1; if (db_fwriteInt(stream, data->item.weapon.ammoTypePid) == -1) return -1; break; case ITEM_TYPE_AMMO: if (db_fwriteInt(stream, data->item.ammo.quantity) == -1) return -1; break; case ITEM_TYPE_MISC: if (db_fwriteInt(stream, data->item.misc.charges) == -1) return -1; break; case ITEM_TYPE_KEY: if (db_fwriteInt(stream, data->item.key.keyCode) == -1) return -1; break; } break; case OBJ_TYPE_SCENERY: if (proto_ptr(obj->pid, &proto) == -1) return -1; switch (proto->scenery.type) { case SCENERY_TYPE_DOOR: if (db_fwriteInt(stream, data->scenery.door.openFlags) == -1) return -1; break; case SCENERY_TYPE_STAIRS: if (db_fwriteInt(stream, data->scenery.stairs.destinationBuiltTile) == -1) return -1; if (db_fwriteInt(stream, data->scenery.stairs.destinationMap) == -1) return -1; break; case SCENERY_TYPE_ELEVATOR: if (db_fwriteInt(stream, data->scenery.elevator.type) == -1) return -1; if (db_fwriteInt(stream, data->scenery.elevator.level) == -1) return -1; break; case SCENERY_TYPE_LADDER_UP: if (db_fwriteInt(stream, data->scenery.ladder.destinationMap) == -1) return -1; if (db_fwriteInt(stream, data->scenery.ladder.destinationBuiltTile) == -1) return -1; break; case SCENERY_TYPE_LADDER_DOWN: if (db_fwriteInt(stream, data->scenery.ladder.destinationMap) == -1) return -1; if (db_fwriteInt(stream, data->scenery.ladder.destinationBuiltTile) == -1) return -1; break; default: break; } break; case OBJ_TYPE_MISC: if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) { if (db_fwriteInt(stream, data->misc.map) == -1) return -1; if (db_fwriteInt(stream, data->misc.tile) == -1) return -1; if (db_fwriteInt(stream, data->misc.elevation) == -1) return -1; if (db_fwriteInt(stream, data->misc.rotation) == -1) return -1; } break; default: break; } } return 0; } // 0x49F73C int proto_update_gen(Object* obj) { Proto* proto; if (!protos_been_initialized) { return -1; } ObjectData* data = &(obj->data); data->inventory.length = 0; data->inventory.capacity = 0; data->inventory.items = NULL; if (proto_ptr(obj->pid, &proto) == -1) { return -1; } switch (PID_TYPE(obj->pid)) { case OBJ_TYPE_ITEM: switch (proto->item.type) { case ITEM_TYPE_CONTAINER: data->flags = 0; break; case ITEM_TYPE_WEAPON: data->item.weapon.ammoQuantity = proto->item.data.weapon.ammoCapacity; data->item.weapon.ammoTypePid = proto->item.data.weapon.ammoTypePid; break; case ITEM_TYPE_AMMO: data->item.ammo.quantity = proto->item.data.ammo.quantity; break; case ITEM_TYPE_MISC: data->item.misc.charges = proto->item.data.misc.charges; break; case ITEM_TYPE_KEY: data->item.key.keyCode = proto->item.data.key.keyCode; break; } break; case OBJ_TYPE_SCENERY: switch (proto->scenery.type) { case SCENERY_TYPE_DOOR: data->scenery.door.openFlags = proto->scenery.data.door.openFlags; break; case SCENERY_TYPE_STAIRS: data->scenery.stairs.destinationBuiltTile = proto->scenery.data.stairs.field_0; data->scenery.stairs.destinationMap = proto->scenery.data.stairs.field_4; break; case SCENERY_TYPE_ELEVATOR: data->scenery.elevator.type = proto->scenery.data.elevator.type; data->scenery.elevator.level = proto->scenery.data.elevator.level; break; case SCENERY_TYPE_LADDER_UP: case SCENERY_TYPE_LADDER_DOWN: data->scenery.ladder.destinationMap = proto->scenery.data.ladder.field_0; break; } break; case OBJ_TYPE_MISC: if (obj->pid >= 0x5000010 && obj->pid <= 0x5000017) { data->misc.tile = -1; data->misc.elevation = 0; data->misc.rotation = 0; data->misc.map = -1; } break; default: break; } return 0; } // 0x49F8A0 int proto_update_init(Object* obj) { if (!protos_been_initialized) { return -1; } if (obj == NULL) { return -1; } if (obj->pid == -1) { return -1; } for (int i = 0; i < 14; i++) { obj->field_2C_array[i] = 0; } if (PID_TYPE(obj->pid) != OBJ_TYPE_CRITTER) { return proto_update_gen(obj); } ObjectData* data = &(obj->data); data->inventory.length = 0; data->inventory.capacity = 0; data->inventory.items = NULL; combat_data_init(obj); data->critter.hp = critterGetStat(obj, STAT_MAXIMUM_HIT_POINTS); data->critter.combat.ap = critterGetStat(obj, STAT_MAXIMUM_ACTION_POINTS); stat_recalc_derived(obj); obj->data.critter.combat.whoHitMe = NULL; Proto* proto; if (proto_ptr(obj->pid, &proto) != -1) { data->critter.combat.aiPacket = proto->critter.aiPacket; data->critter.combat.team = proto->critter.team; } return 0; } // 0x49F984 int proto_dude_update_gender() { Proto* proto; if (proto_ptr(0x1000000, &proto) == -1) { return -1; } int nativeLook = DUDE_NATIVE_LOOK_TRIBAL; if (gmovie_has_been_played(MOVIE_VSUIT)) { nativeLook = DUDE_NATIVE_LOOK_JUMPSUIT; } int frmId; if (critterGetStat(obj_dude, STAT_GENDER) == GENDER_MALE) { frmId = art_vault_person_nums[nativeLook][GENDER_MALE]; } else { frmId = art_vault_person_nums[nativeLook][GENDER_FEMALE]; } art_vault_guy_num = frmId; if (inven_worn(obj_dude) == NULL) { int v1 = 0; if (inven_right_hand(obj_dude) != NULL || inven_left_hand(obj_dude) != NULL) { v1 = (obj_dude->fid & 0xF000) >> 12; } int fid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, v1, 0); obj_change_fid(obj_dude, fid, NULL); } proto->fid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, 0, 0); return 0; } // 0x49FA64 int proto_dude_init(const char* path) { // 0x51C538 static int init_true = 0; // 0x51C53C static int retval = 0; pc_proto.fid = art_id(OBJ_TYPE_CRITTER, art_vault_guy_num, 0, 0, 0); if (init_true) { obj_inven_free(&(obj_dude->data.inventory)); } init_true = 1; Proto* proto; if (proto_ptr(0x1000000, &proto) == -1) { return -1; } proto_ptr(obj_dude->pid, &proto); proto_update_init(obj_dude); obj_dude->data.critter.combat.aiPacket = 0; obj_dude->data.critter.combat.team = 0; ResetPlayer(); if (pc_load_data(path) == -1) { retval = -1; } proto->critter.data.baseStats[STAT_DAMAGE_RESISTANCE_EMP] = 100; proto->critter.data.bodyType = 0; proto->critter.data.experience = 0; proto->critter.data.killType = 0; proto->critter.data.damageType = 0; proto_dude_update_gender(); inven_reset_dude(); if ((obj_dude->flags & OBJECT_FLAT) != 0) { obj_toggle_flat(obj_dude, NULL); } if ((obj_dude->flags & OBJECT_NO_BLOCK) != 0) { obj_dude->flags &= ~OBJECT_NO_BLOCK; } stat_recalc_derived(obj_dude); critter_adjust_hits(obj_dude, 10000); if (retval) { debug_printf("\n ** Error in proto_dude_init()! **\n"); } return 0; } // 0x49FFD8 int proto_data_member(int pid, int member, ProtoDataMemberValue* value) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return -1; } switch (PID_TYPE(pid)) { case OBJ_TYPE_ITEM: switch (member) { case ITEM_DATA_MEMBER_PID: value->integerValue = proto->pid; break; case ITEM_DATA_MEMBER_NAME: // NOTE: uninline value->stringValue = proto_name(proto->scenery.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case ITEM_DATA_MEMBER_DESCRIPTION: // NOTE: Uninline. value->stringValue = proto_description(proto->pid); return PROTO_DATA_MEMBER_TYPE_STRING; case ITEM_DATA_MEMBER_FID: value->integerValue = proto->fid; break; case ITEM_DATA_MEMBER_LIGHT_DISTANCE: value->integerValue = proto->item.lightDistance; break; case ITEM_DATA_MEMBER_LIGHT_INTENSITY: value->integerValue = proto->item.lightIntensity; break; case ITEM_DATA_MEMBER_FLAGS: value->integerValue = proto->item.flags; break; case ITEM_DATA_MEMBER_EXTENDED_FLAGS: value->integerValue = proto->item.extendedFlags; break; case ITEM_DATA_MEMBER_SID: value->integerValue = proto->item.sid; break; case ITEM_DATA_MEMBER_TYPE: value->integerValue = proto->item.type; break; case ITEM_DATA_MEMBER_MATERIAL: value->integerValue = proto->item.material; break; case ITEM_DATA_MEMBER_SIZE: value->integerValue = proto->item.size; break; case ITEM_DATA_MEMBER_WEIGHT: value->integerValue = proto->item.weight; break; case ITEM_DATA_MEMBER_COST: value->integerValue = proto->item.cost; break; case ITEM_DATA_MEMBER_INVENTORY_FID: value->integerValue = proto->item.inventoryFid; break; case ITEM_DATA_MEMBER_WEAPON_RANGE: if (proto->item.type == ITEM_TYPE_WEAPON) { value->integerValue = proto->item.data.weapon.maxRange1; } break; default: debug_printf("\n\tError: Unimp'd data member in member in proto_data_member!"); break; } break; case OBJ_TYPE_CRITTER: switch (member) { case CRITTER_DATA_MEMBER_PID: value->integerValue = proto->critter.pid; break; case CRITTER_DATA_MEMBER_NAME: // NOTE: Uninline. value->stringValue = proto_name(proto->critter.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case CRITTER_DATA_MEMBER_DESCRIPTION: // NOTE: Uninline. value->stringValue = proto_description(proto->critter.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case CRITTER_DATA_MEMBER_FID: value->integerValue = proto->critter.fid; break; case CRITTER_DATA_MEMBER_LIGHT_DISTANCE: value->integerValue = proto->critter.lightDistance; break; case CRITTER_DATA_MEMBER_LIGHT_INTENSITY: value->integerValue = proto->critter.lightIntensity; break; case CRITTER_DATA_MEMBER_FLAGS: value->integerValue = proto->critter.flags; break; case CRITTER_DATA_MEMBER_EXTENDED_FLAGS: value->integerValue = proto->critter.extendedFlags; break; case CRITTER_DATA_MEMBER_SID: value->integerValue = proto->critter.sid; break; case CRITTER_DATA_MEMBER_HEAD_FID: value->integerValue = proto->critter.headFid; break; case CRITTER_DATA_MEMBER_BODY_TYPE: value->integerValue = proto->critter.data.bodyType; break; default: debug_printf("\n\tError: Unimp'd data member in member in proto_data_member!"); break; } break; case OBJ_TYPE_SCENERY: switch (member) { case SCENERY_DATA_MEMBER_PID: value->integerValue = proto->scenery.pid; break; case SCENERY_DATA_MEMBER_NAME: // NOTE: Uninline. value->stringValue = proto_name(proto->scenery.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case SCENERY_DATA_MEMBER_DESCRIPTION: // NOTE: Uninline. value->stringValue = proto_description(proto->scenery.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case SCENERY_DATA_MEMBER_FID: value->integerValue = proto->scenery.fid; break; case SCENERY_DATA_MEMBER_LIGHT_DISTANCE: value->integerValue = proto->scenery.lightDistance; break; case SCENERY_DATA_MEMBER_LIGHT_INTENSITY: value->integerValue = proto->scenery.lightIntensity; break; case SCENERY_DATA_MEMBER_FLAGS: value->integerValue = proto->scenery.flags; break; case SCENERY_DATA_MEMBER_EXTENDED_FLAGS: value->integerValue = proto->scenery.extendedFlags; break; case SCENERY_DATA_MEMBER_SID: value->integerValue = proto->scenery.sid; break; case SCENERY_DATA_MEMBER_TYPE: value->integerValue = proto->scenery.type; break; case SCENERY_DATA_MEMBER_MATERIAL: value->integerValue = proto->scenery.field_2C; break; default: debug_printf("\n\tError: Unimp'd data member in member in proto_data_member!"); break; } break; case OBJ_TYPE_WALL: switch (member) { case WALL_DATA_MEMBER_PID: value->integerValue = proto->wall.pid; break; case WALL_DATA_MEMBER_NAME: // NOTE: Uninline. value->stringValue = proto_name(proto->wall.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case WALL_DATA_MEMBER_DESCRIPTION: // NOTE: Uninline. value->stringValue = proto_description(proto->wall.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case WALL_DATA_MEMBER_FID: value->integerValue = proto->wall.fid; break; case WALL_DATA_MEMBER_LIGHT_DISTANCE: value->integerValue = proto->wall.lightDistance; break; case WALL_DATA_MEMBER_LIGHT_INTENSITY: value->integerValue = proto->wall.lightIntensity; break; case WALL_DATA_MEMBER_FLAGS: value->integerValue = proto->wall.flags; break; case WALL_DATA_MEMBER_EXTENDED_FLAGS: value->integerValue = proto->wall.extendedFlags; break; case WALL_DATA_MEMBER_SID: value->integerValue = proto->wall.sid; break; case WALL_DATA_MEMBER_MATERIAL: value->integerValue = proto->wall.material; break; default: debug_printf("\n\tError: Unimp'd data member in member in proto_data_member!"); break; } break; case OBJ_TYPE_TILE: debug_printf("\n\tError: Unimp'd data member in member in proto_data_member!"); break; case OBJ_TYPE_MISC: switch (member) { case MISC_DATA_MEMBER_PID: value->integerValue = proto->misc.pid; break; case MISC_DATA_MEMBER_NAME: // NOTE: Uninline. value->stringValue = proto_name(proto->misc.pid); return PROTO_DATA_MEMBER_TYPE_STRING; case MISC_DATA_MEMBER_DESCRIPTION: // NOTE: Uninline. value->stringValue = proto_description(proto->misc.pid); // FIXME: Errornously report type as int, should be string. return PROTO_DATA_MEMBER_TYPE_INT; case MISC_DATA_MEMBER_FID: value->integerValue = proto->misc.fid; return 1; case MISC_DATA_MEMBER_LIGHT_DISTANCE: value->integerValue = proto->misc.lightDistance; return 1; case MISC_DATA_MEMBER_LIGHT_INTENSITY: value->integerValue = proto->misc.lightIntensity; break; case MISC_DATA_MEMBER_FLAGS: value->integerValue = proto->misc.flags; break; case MISC_DATA_MEMBER_EXTENDED_FLAGS: value->integerValue = proto->misc.extendedFlags; break; default: debug_printf("\n\tError: Unimp'd data member in member in proto_data_member!"); break; } break; } return PROTO_DATA_MEMBER_TYPE_INT; } // 0x4A0390 int proto_init() { char* master_patches; int len; MessageListItem messageListItem; char path[MAX_PATH]; int i; if (!config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &master_patches)) { return -1; } sprintf(path, "%s\\proto", master_patches); len = strlen(path); mkdir(path); strcpy(path + len, "\\critters"); mkdir(path); strcpy(path + len, "\\items"); mkdir(path); // TODO: Get rid of cast. proto_critter_init((Proto*)&pc_proto, 0x1000000); pc_proto.pid = 0x1000000; pc_proto.fid = art_id(OBJ_TYPE_CRITTER, 1, 0, 0, 0); obj_dude->pid = 0x1000000; obj_dude->sid = 1; for (i = 0; i < 6; i++) { proto_remove_list(i); } proto_header_load(); protos_been_initialized = 1; proto_dude_init("premade\\player.gcd"); for (i = 0; i < 6; i++) { if (!message_init(&(proto_msg_files[i]))) { debug_printf("\nError: Initing proto message files!"); return -1; } } for (i = 0; i < 6; i++) { sprintf(path, "%spro_%.4s%s", msg_path, art_dir(i), ".msg"); if (!message_load(&(proto_msg_files[i]), path)) { debug_printf("\nError: Loading proto message files!"); return -1; } } mp_critter_stats_list[0] = "Drug Stat (Special)"; mp_critter_stats_list[1] = "None"; critter_stats_list = &(mp_critter_stats_list[2]); for (i = 0; i < STAT_COUNT; i++) { critter_stats_list[i] = stat_name(i); if (critter_stats_list[i] == NULL) { debug_printf("\nError: Finding stat names!"); return -1; } } mp_perk_code_strs[0] = "None"; perk_code_strs = &(mp_perk_code_strs[1]); for (i = 0; i < PERK_COUNT; i++) { mp_perk_code_strs[i] = perk_name(i); if (mp_perk_code_strs[i] == NULL) { debug_printf("\nError: Finding perk names!"); return -1; } } if (!message_init(&proto_main_msg_file)) { debug_printf("\nError: Initing main proto message file!"); return -1; } sprintf(path, "%sproto.msg", msg_path); if (!message_load(&proto_main_msg_file, path)) { debug_printf("\nError: Loading main proto message file!"); return -1; } proto_none_str = getmsg(&proto_main_msg_file, &messageListItem, 10); // material type names for (i = 0; i < MATERIAL_TYPE_COUNT; i++) { item_pro_material[i] = getmsg(&proto_main_msg_file, &messageListItem, 100 + i); } // item type names for (i = 0; i < ITEM_TYPE_COUNT; i++) { item_pro_type[i] = getmsg(&proto_main_msg_file, &messageListItem, 150 + i); } // scenery type names for (i = 0; i < SCENERY_TYPE_COUNT; i++) { scenery_pro_type[i] = getmsg(&proto_main_msg_file, &messageListItem, 200 + i); } // damage code types for (i = 0; i < DAMAGE_TYPE_COUNT; i++) { damage_code_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 250 + i); } // caliber types for (i = 0; i < CALIBER_TYPE_COUNT; i++) { cal_type_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 300 + i); } // race types for (i = 0; i < RACE_TYPE_COUNT; i++) { race_type_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 350 + i); } // body types for (i = 0; i < BODY_TYPE_COUNT; i++) { body_type_strs[i] = getmsg(&proto_main_msg_file, &messageListItem, 400 + i); } return 0; } // 0x4A0814 void proto_reset() { int i; // TODO: Get rid of cast. proto_critter_init((Proto*)&pc_proto, 0x1000000); pc_proto.pid = 0x1000000; pc_proto.fid = art_id(OBJ_TYPE_CRITTER, 1, 0, 0, 0); obj_dude->pid = 0x1000000; obj_dude->sid = -1; obj_dude->flags &= ~OBJECT_FLAG_0xFC000; for (i = 0; i < 6; i++) { proto_remove_list(i); } proto_header_load(); protos_been_initialized = 1; proto_dude_init("premade\\player.gcd"); } // 0x4A0898 void proto_exit() { int i; for (i = 0; i < 6; i++) { proto_remove_list(i); } for (i = 0; i < 6; i++) { message_exit(&(proto_msg_files[i])); } message_exit(&proto_main_msg_file); } // Count .pro lines in .lst files. // // 0x4A08E0 int proto_header_load() { for (int index = 0; index < 6; index++) { ProtoList* ptr = &(protolists[index]); ptr->head = NULL; ptr->tail = NULL; ptr->length = 0; ptr->max_entries_num = 1; char path[MAX_PATH]; proto_make_path(path, index << 24); strcat(path, "\\"); strcat(path, art_dir(index)); strcat(path, ".lst"); File* stream = db_fopen(path, "rt"); if (stream == NULL) { return -1; } int ch = '\0'; while (1) { ch = db_fgetc(stream); if (ch == -1) { break; } if (ch == '\n') { ptr->max_entries_num++; } } if (ch != '\n') { ptr->max_entries_num++; } db_fclose(stream); } return 0; } // 0x4A0AEC static int proto_read_item_data(ItemProtoData* item_data, int type, File* stream) { switch (type) { case ITEM_TYPE_ARMOR: if (db_freadInt(stream, &(item_data->armor.armorClass)) == -1) return -1; if (db_freadIntCount(stream, item_data->armor.damageResistance, 7) == -1) return -1; if (db_freadIntCount(stream, item_data->armor.damageThreshold, 7) == -1) return -1; if (db_freadInt(stream, &(item_data->armor.perk)) == -1) return -1; if (db_freadInt(stream, &(item_data->armor.maleFid)) == -1) return -1; if (db_freadInt(stream, &(item_data->armor.femaleFid)) == -1) return -1; return 0; case ITEM_TYPE_CONTAINER: if (db_freadInt(stream, &(item_data->container.maxSize)) == -1) return -1; if (db_freadInt(stream, &(item_data->container.openFlags)) == -1) return -1; return 0; case ITEM_TYPE_DRUG: if (db_freadInt(stream, &(item_data->drug.stat[0])) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.stat[1])) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.stat[2])) == -1) return -1; if (db_freadIntCount(stream, item_data->drug.amount, 3) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.duration1)) == -1) return -1; if (db_freadIntCount(stream, item_data->drug.amount1, 3) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.duration2)) == -1) return -1; if (db_freadIntCount(stream, item_data->drug.amount2, 3) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.addictionChance)) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.withdrawalEffect)) == -1) return -1; if (db_freadInt(stream, &(item_data->drug.withdrawalOnset)) == -1) return -1; return 0; case ITEM_TYPE_WEAPON: if (db_freadInt(stream, &(item_data->weapon.animationCode)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.minDamage)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.maxDamage)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.damageType)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.maxRange1)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.maxRange2)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.projectilePid)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.minStrength)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.actionPointCost1)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.actionPointCost2)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.criticalFailureType)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.perk)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.rounds)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.caliber)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.ammoTypePid)) == -1) return -1; if (db_freadInt(stream, &(item_data->weapon.ammoCapacity)) == -1) return -1; if (db_freadByte(stream, &(item_data->weapon.soundCode)) == -1) return -1; return 0; case ITEM_TYPE_AMMO: if (db_freadInt(stream, &(item_data->ammo.caliber)) == -1) return -1; if (db_freadInt(stream, &(item_data->ammo.quantity)) == -1) return -1; if (db_freadInt(stream, &(item_data->ammo.armorClassModifier)) == -1) return -1; if (db_freadInt(stream, &(item_data->ammo.damageResistanceModifier)) == -1) return -1; if (db_freadInt(stream, &(item_data->ammo.damageMultiplier)) == -1) return -1; if (db_freadInt(stream, &(item_data->ammo.damageDivisor)) == -1) return -1; return 0; case ITEM_TYPE_MISC: if (db_freadInt(stream, &(item_data->misc.powerTypePid)) == -1) return -1; if (db_freadInt(stream, &(item_data->misc.powerType)) == -1) return -1; if (db_freadInt(stream, &(item_data->misc.charges)) == -1) return -1; return 0; case ITEM_TYPE_KEY: if (db_freadInt(stream, &(item_data->key.keyCode)) == -1) return -1; return 0; } return 0; } // 0x4A0ED0 static int proto_read_scenery_data(SceneryProtoData* scenery_data, int type, File* stream) { switch (type) { case SCENERY_TYPE_DOOR: if (db_freadInt(stream, &(scenery_data->door.openFlags)) == -1) return -1; if (db_freadInt(stream, &(scenery_data->door.keyCode)) == -1) return -1; return 0; case SCENERY_TYPE_STAIRS: if (db_freadInt(stream, &(scenery_data->stairs.field_0)) == -1) return -1; if (db_freadInt(stream, &(scenery_data->stairs.field_4)) == -1) return -1; return 0; case SCENERY_TYPE_ELEVATOR: if (db_freadInt(stream, &(scenery_data->elevator.type)) == -1) return -1; if (db_freadInt(stream, &(scenery_data->elevator.level)) == -1) return -1; return 0; case SCENERY_TYPE_LADDER_UP: case SCENERY_TYPE_LADDER_DOWN: if (db_freadInt(stream, &(scenery_data->ladder.field_0)) == -1) return -1; return 0; case SCENERY_TYPE_GENERIC: if (db_freadInt(stream, &(scenery_data->generic.field_0)) == -1) return -1; return 0; } return 0; } // read .pro file // 0x4A0FA0 static int proto_read_protoSubNode(Proto* proto, File* stream) { if (db_freadInt(stream, &(proto->pid)) == -1) return -1; if (db_freadInt(stream, &(proto->messageId)) == -1) return -1; if (db_freadInt(stream, &(proto->fid)) == -1) return -1; switch (PID_TYPE(proto->pid)) { case OBJ_TYPE_ITEM: if (db_freadInt(stream, &(proto->item.lightDistance)) == -1) return -1; if (db_freadLong(stream, &(proto->item.lightIntensity)) == -1) return -1; if (db_freadInt(stream, &(proto->item.flags)) == -1) return -1; if (db_freadInt(stream, &(proto->item.extendedFlags)) == -1) return -1; if (db_freadInt(stream, &(proto->item.sid)) == -1) return -1; if (db_freadInt(stream, &(proto->item.type)) == -1) return -1; if (db_freadInt(stream, &(proto->item.material)) == -1) return -1; if (db_freadInt(stream, &(proto->item.size)) == -1) return -1; if (db_freadLong(stream, &(proto->item.weight)) == -1) return -1; if (db_freadInt(stream, &(proto->item.cost)) == -1) return -1; if (db_freadInt(stream, &(proto->item.inventoryFid)) == -1) return -1; if (db_freadByte(stream, &(proto->item.field_80)) == -1) return -1; if (proto_read_item_data(&(proto->item.data), proto->item.type, stream) == -1) return -1; return 0; case OBJ_TYPE_CRITTER: if (db_freadInt(stream, &(proto->critter.lightDistance)) == -1) return -1; if (db_freadLong(stream, &(proto->critter.lightIntensity)) == -1) return -1; if (db_freadInt(stream, &(proto->critter.flags)) == -1) return -1; if (db_freadInt(stream, &(proto->critter.extendedFlags)) == -1) return -1; if (db_freadInt(stream, &(proto->critter.sid)) == -1) return -1; if (db_freadInt(stream, &(proto->critter.headFid)) == -1) return -1; if (db_freadInt(stream, &(proto->critter.aiPacket)) == -1) return -1; if (db_freadInt(stream, &(proto->critter.team)) == -1) return -1; if (critter_read_data(stream, &(proto->critter.data)) == -1) return -1; return 0; case OBJ_TYPE_SCENERY: if (db_freadInt(stream, &(proto->scenery.lightDistance)) == -1) return -1; if (db_freadLong(stream, &(proto->scenery.lightIntensity)) == -1) return -1; if (db_freadInt(stream, &(proto->scenery.flags)) == -1) return -1; if (db_freadInt(stream, &(proto->scenery.extendedFlags)) == -1) return -1; if (db_freadInt(stream, &(proto->scenery.sid)) == -1) return -1; if (db_freadInt(stream, &(proto->scenery.type)) == -1) return -1; if (db_freadInt(stream, &(proto->scenery.field_2C)) == -1) return -1; if (db_freadByte(stream, &(proto->scenery.field_34)) == -1) return -1; if (proto_read_scenery_data(&(proto->scenery.data), proto->scenery.type, stream) == -1) return -1; return 0; case OBJ_TYPE_WALL: if (db_freadInt(stream, &(proto->wall.lightDistance)) == -1) return -1; if (db_freadLong(stream, &(proto->wall.lightIntensity)) == -1) return -1; if (db_freadInt(stream, &(proto->wall.flags)) == -1) return -1; if (db_freadInt(stream, &(proto->wall.extendedFlags)) == -1) return -1; if (db_freadInt(stream, &(proto->wall.sid)) == -1) return -1; if (db_freadInt(stream, &(proto->wall.material)) == -1) return -1; return 0; case OBJ_TYPE_TILE: if (db_freadInt(stream, &(proto->tile.flags)) == -1) return -1; if (db_freadInt(stream, &(proto->tile.extendedFlags)) == -1) return -1; if (db_freadInt(stream, &(proto->tile.sid)) == -1) return -1; if (db_freadInt(stream, &(proto->tile.material)) == -1) return -1; return 0; case OBJ_TYPE_MISC: if (db_freadInt(stream, &(proto->misc.lightDistance)) == -1) return -1; if (db_freadLong(stream, &(proto->misc.lightIntensity)) == -1) return -1; if (db_freadInt(stream, &(proto->misc.flags)) == -1) return -1; if (db_freadInt(stream, &(proto->misc.extendedFlags)) == -1) return -1; return 0; } return -1; } // 0x4A1390 static int proto_write_item_data(ItemProtoData* item_data, int type, File* stream) { switch (type) { case ITEM_TYPE_ARMOR: if (db_fwriteInt(stream, item_data->armor.armorClass) == -1) return -1; if (db_fwriteIntCount(stream, item_data->armor.damageResistance, 7) == -1) return -1; if (db_fwriteIntCount(stream, item_data->armor.damageThreshold, 7) == -1) return -1; if (db_fwriteInt(stream, item_data->armor.perk) == -1) return -1; if (db_fwriteInt(stream, item_data->armor.maleFid) == -1) return -1; if (db_fwriteInt(stream, item_data->armor.femaleFid) == -1) return -1; return 0; case ITEM_TYPE_CONTAINER: if (db_fwriteInt(stream, item_data->container.maxSize) == -1) return -1; if (db_fwriteInt(stream, item_data->container.openFlags) == -1) return -1; return 0; case ITEM_TYPE_DRUG: if (db_fwriteInt(stream, item_data->drug.stat[0]) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.stat[1]) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.stat[2]) == -1) return -1; if (db_fwriteIntCount(stream, item_data->drug.amount, 3) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.duration1) == -1) return -1; if (db_fwriteIntCount(stream, item_data->drug.amount1, 3) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.duration2) == -1) return -1; if (db_fwriteIntCount(stream, item_data->drug.amount2, 3) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.addictionChance) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.withdrawalEffect) == -1) return -1; if (db_fwriteInt(stream, item_data->drug.withdrawalOnset) == -1) return -1; return 0; case ITEM_TYPE_WEAPON: if (db_fwriteInt(stream, item_data->weapon.animationCode) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.maxDamage) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.minDamage) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.damageType) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.maxRange1) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.maxRange2) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.projectilePid) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.minStrength) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.actionPointCost1) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.actionPointCost2) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.criticalFailureType) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.perk) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.rounds) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.caliber) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.ammoTypePid) == -1) return -1; if (db_fwriteInt(stream, item_data->weapon.ammoCapacity) == -1) return -1; if (db_fwriteByte(stream, item_data->weapon.soundCode) == -1) return -1; return 0; case ITEM_TYPE_AMMO: if (db_fwriteInt(stream, item_data->ammo.caliber) == -1) return -1; if (db_fwriteInt(stream, item_data->ammo.quantity) == -1) return -1; if (db_fwriteInt(stream, item_data->ammo.armorClassModifier) == -1) return -1; if (db_fwriteInt(stream, item_data->ammo.damageResistanceModifier) == -1) return -1; if (db_fwriteInt(stream, item_data->ammo.damageMultiplier) == -1) return -1; if (db_fwriteInt(stream, item_data->ammo.damageDivisor) == -1) return -1; return 0; case ITEM_TYPE_MISC: if (db_fwriteInt(stream, item_data->misc.powerTypePid) == -1) return -1; if (db_fwriteInt(stream, item_data->misc.powerType) == -1) return -1; if (db_fwriteInt(stream, item_data->misc.charges) == -1) return -1; return 0; case ITEM_TYPE_KEY: if (db_fwriteInt(stream, item_data->key.keyCode) == -1) return -1; return 0; } return 0; } // 0x4A16E4 static int proto_write_scenery_data(SceneryProtoData* scenery_data, int type, File* stream) { switch (type) { case SCENERY_TYPE_DOOR: if (db_fwriteInt(stream, scenery_data->door.openFlags) == -1) return -1; if (db_fwriteInt(stream, scenery_data->door.keyCode) == -1) return -1; return 0; case SCENERY_TYPE_STAIRS: if (db_fwriteInt(stream, scenery_data->stairs.field_0) == -1) return -1; if (db_fwriteInt(stream, scenery_data->stairs.field_4) == -1) return -1; return 0; case SCENERY_TYPE_ELEVATOR: if (db_fwriteInt(stream, scenery_data->elevator.type) == -1) return -1; if (db_fwriteInt(stream, scenery_data->elevator.level) == -1) return -1; return 0; case SCENERY_TYPE_LADDER_UP: case SCENERY_TYPE_LADDER_DOWN: if (db_fwriteInt(stream, scenery_data->ladder.field_0) == -1) return -1; return 0; case SCENERY_TYPE_GENERIC: if (db_fwriteInt(stream, scenery_data->generic.field_0) == -1) return -1; return 0; } return 0; } // 0x4A17B4 static int proto_write_protoSubNode(Proto* proto, File* stream) { if (db_fwriteInt(stream, proto->pid) == -1) return -1; if (db_fwriteInt(stream, proto->messageId) == -1) return -1; if (db_fwriteInt(stream, proto->fid) == -1) return -1; switch (PID_TYPE(proto->pid)) { case OBJ_TYPE_ITEM: if (db_fwriteInt(stream, proto->item.lightDistance) == -1) return -1; if (db_fwriteLong(stream, proto->item.lightIntensity) == -1) return -1; if (db_fwriteInt(stream, proto->item.flags) == -1) return -1; if (db_fwriteInt(stream, proto->item.extendedFlags) == -1) return -1; if (db_fwriteInt(stream, proto->item.sid) == -1) return -1; if (db_fwriteInt(stream, proto->item.type) == -1) return -1; if (db_fwriteInt(stream, proto->item.material) == -1) return -1; if (db_fwriteInt(stream, proto->item.size) == -1) return -1; if (db_fwriteLong(stream, proto->item.weight) == -1) return -1; if (db_fwriteInt(stream, proto->item.cost) == -1) return -1; if (db_fwriteInt(stream, proto->item.inventoryFid) == -1) return -1; if (db_fwriteByte(stream, proto->item.field_80) == -1) return -1; if (proto_write_item_data(&(proto->item.data), proto->item.type, stream) == -1) return -1; return 0; case OBJ_TYPE_CRITTER: if (db_fwriteInt(stream, proto->critter.lightDistance) == -1) return -1; if (db_fwriteLong(stream, proto->critter.lightIntensity) == -1) return -1; if (db_fwriteInt(stream, proto->critter.flags) == -1) return -1; if (db_fwriteInt(stream, proto->critter.extendedFlags) == -1) return -1; if (db_fwriteInt(stream, proto->critter.sid) == -1) return -1; if (db_fwriteInt(stream, proto->critter.headFid) == -1) return -1; if (db_fwriteInt(stream, proto->critter.aiPacket) == -1) return -1; if (db_fwriteInt(stream, proto->critter.team) == -1) return -1; if (critter_write_data(stream, &(proto->critter.data)) == -1) return -1; return 0; case OBJ_TYPE_SCENERY: if (db_fwriteInt(stream, proto->scenery.lightDistance) == -1) return -1; if (db_fwriteLong(stream, proto->scenery.lightIntensity) == -1) return -1; if (db_fwriteInt(stream, proto->scenery.flags) == -1) return -1; if (db_fwriteInt(stream, proto->scenery.extendedFlags) == -1) return -1; if (db_fwriteInt(stream, proto->scenery.sid) == -1) return -1; if (db_fwriteInt(stream, proto->scenery.type) == -1) return -1; if (db_fwriteInt(stream, proto->scenery.field_2C) == -1) return -1; if (db_fwriteByte(stream, proto->scenery.field_34) == -1) return -1; if (proto_write_scenery_data(&(proto->scenery.data), proto->scenery.type, stream) == -1) return -1; case OBJ_TYPE_WALL: if (db_fwriteInt(stream, proto->wall.lightDistance) == -1) return -1; if (db_fwriteLong(stream, proto->wall.lightIntensity) == -1) return -1; if (db_fwriteInt(stream, proto->wall.flags) == -1) return -1; if (db_fwriteInt(stream, proto->wall.extendedFlags) == -1) return -1; if (db_fwriteInt(stream, proto->wall.sid) == -1) return -1; if (db_fwriteInt(stream, proto->wall.material) == -1) return -1; return 0; case OBJ_TYPE_TILE: if (db_fwriteInt(stream, proto->tile.flags) == -1) return -1; if (db_fwriteInt(stream, proto->tile.extendedFlags) == -1) return -1; if (db_fwriteInt(stream, proto->tile.sid) == -1) return -1; if (db_fwriteInt(stream, proto->tile.material) == -1) return -1; return 0; case OBJ_TYPE_MISC: if (db_fwriteInt(stream, proto->misc.lightDistance) == -1) return -1; if (db_fwriteLong(stream, proto->misc.lightIntensity) == -1) return -1; if (db_fwriteInt(stream, proto->misc.flags) == -1) return -1; if (db_fwriteInt(stream, proto->misc.extendedFlags) == -1) return -1; return 0; } return -1; } // 0x4A1B30 int proto_save_pid(int pid) { Proto* proto; if (proto_ptr(pid, &proto) == -1) { return -1; } char path[MAX_PATH]; proto_make_path(path, pid); strcat(path, "\\"); proto_list_str(pid, path + strlen(path)); File* stream = db_fopen(path, "wb"); if (stream == NULL) { return -1; } int rc = proto_write_protoSubNode(proto, stream); db_fclose(stream); return rc; } // 0x4A1C3C int proto_load_pid(int pid, Proto** protoPtr) { char path[MAX_PATH]; proto_make_path(path, pid); strcat(path, "\\"); if (proto_list_str(pid, path + strlen(path)) == -1) { return -1; } File* stream = db_fopen(path, "rb"); if (stream == NULL) { debug_printf("\nError: Can't fopen proto!\n"); *protoPtr = NULL; return -1; } if (proto_find_free_subnode(PID_TYPE(pid), protoPtr) == -1) { db_fclose(stream); return -1; } if (proto_read_protoSubNode(*protoPtr, stream) != 0) { db_fclose(stream); return -1; } db_fclose(stream); return 0; } // 0x4A1D98 int proto_find_free_subnode(int type, Proto** protoPtr) { size_t size = (type >= 0 && type < 11) ? proto_sizes[type] : 0; Proto* proto = (Proto*)mem_malloc(size); *protoPtr = proto; if (proto == NULL) { return -1; } ProtoList* protoList = &(protolists[type]); ProtoListExtent* protoListExtent = protoList->tail; if (protoList->head != NULL) { if (protoListExtent->length == PROTO_LIST_EXTENT_SIZE) { ProtoListExtent* newExtent = protoListExtent->next = (ProtoListExtent*)mem_malloc(sizeof(ProtoListExtent)); if (protoListExtent == NULL) { mem_free(proto); *protoPtr = NULL; return -1; } newExtent->length = 0; newExtent->next = NULL; protoList->tail = newExtent; protoList->length++; protoListExtent = newExtent; } } else { protoListExtent = (ProtoListExtent*)mem_malloc(sizeof(ProtoListExtent)); if (protoListExtent == NULL) { mem_free(proto); *protoPtr = NULL; return -1; } protoListExtent->next = NULL; protoListExtent->length = 0; protoList->length = 1; protoList->tail = protoListExtent; protoList->head = protoListExtent; } protoListExtent->proto[protoListExtent->length] = proto; protoListExtent->length++; return 0; } // Evict top most proto cache block. // // 0x4A2040 static void proto_remove_some_list(int type) { ProtoList* protoList = &(protolists[type]); ProtoListExtent* protoListExtent = protoList->head; if (protoListExtent != NULL) { protoList->length--; protoList->head = protoListExtent->next; for (int index = 0; index < protoListExtent->length; index++) { mem_free(protoListExtent->proto[index]); } mem_free(protoListExtent); } } // Clear proto cache of given type. // // 0x4A2094 static void proto_remove_list(int type) { ProtoList* protoList = &(protolists[type]); ProtoListExtent* curr = protoList->head; while (curr != NULL) { ProtoListExtent* next = curr->next; for (int index = 0; index < curr->length; index++) { mem_free(curr->proto[index]); } mem_free(curr); curr = next; } protoList->head = NULL; protoList->tail = NULL; protoList->length = 0; } // Clear all proto cache. // // 0x4A20F4 void proto_remove_all() { for (int index = 0; index < 6; index++) { proto_remove_list(index); } } // 0x4A2108 int proto_ptr(int pid, Proto** protoPtr) { *protoPtr = NULL; if (pid == -1) { return -1; } if (pid == 0x1000000) { *protoPtr = (Proto*)&pc_proto; return 0; } ProtoList* protoList = &(protolists[PID_TYPE(pid)]); ProtoListExtent* protoListExtent = protoList->head; while (protoListExtent != NULL) { for (int index = 0; index < protoListExtent->length; index++) { Proto* proto = (Proto*)protoListExtent->proto[index]; if (pid == proto->pid) { *protoPtr = proto; return 0; } } protoListExtent = protoListExtent->next; } if (protoList->head != NULL && protoList->tail != NULL) { if (PROTO_LIST_EXTENT_SIZE * protoList->length - (PROTO_LIST_EXTENT_SIZE - protoList->tail->length) > PROTO_LIST_MAX_ENTRIES) { proto_remove_some_list(PID_TYPE(pid)); } } return proto_load_pid(pid, protoPtr); } // 0x4A21DC static int proto_new_id(int a1) { int result = protolists[a1].max_entries_num; protolists[a1].max_entries_num = result + 1; return result; } // 0x4A2214 int proto_max_id(int a1) { return protolists[a1].max_entries_num; } // 0x4A22C0 int ResetPlayer() { Proto* proto; proto_ptr(obj_dude->pid, &proto); stat_pc_set_defaults(); stat_set_defaults(&(proto->critter.data)); critter_reset(); editor_reset(); skill_set_defaults(&(proto->critter.data)); skill_reset(); perk_reset(); trait_reset(); stat_recalc_derived(obj_dude); return 0; } ================================================ FILE: src/game/proto.h ================================================ #ifndef FALLOUT_GAME_PROTO_H_ #define FALLOUT_GAME_PROTO_H_ #include "plib/db/db.h" #include "game/message.h" #include "game/object_types.h" #include "game/perk_defs.h" #include "game/proto_types.h" #include "game/skill_defs.h" #include "game/stat_defs.h" typedef enum ItemDataMember { ITEM_DATA_MEMBER_PID = 0, ITEM_DATA_MEMBER_NAME = 1, ITEM_DATA_MEMBER_DESCRIPTION = 2, ITEM_DATA_MEMBER_FID = 3, ITEM_DATA_MEMBER_LIGHT_DISTANCE = 4, ITEM_DATA_MEMBER_LIGHT_INTENSITY = 5, ITEM_DATA_MEMBER_FLAGS = 6, ITEM_DATA_MEMBER_EXTENDED_FLAGS = 7, ITEM_DATA_MEMBER_SID = 8, ITEM_DATA_MEMBER_TYPE = 9, ITEM_DATA_MEMBER_MATERIAL = 11, ITEM_DATA_MEMBER_SIZE = 12, ITEM_DATA_MEMBER_WEIGHT = 13, ITEM_DATA_MEMBER_COST = 14, ITEM_DATA_MEMBER_INVENTORY_FID = 15, ITEM_DATA_MEMBER_WEAPON_RANGE = 555, } ItemDataMember; typedef enum CritterDataMember { CRITTER_DATA_MEMBER_PID = 0, CRITTER_DATA_MEMBER_NAME = 1, CRITTER_DATA_MEMBER_DESCRIPTION = 2, CRITTER_DATA_MEMBER_FID = 3, CRITTER_DATA_MEMBER_LIGHT_DISTANCE = 4, CRITTER_DATA_MEMBER_LIGHT_INTENSITY = 5, CRITTER_DATA_MEMBER_FLAGS = 6, CRITTER_DATA_MEMBER_EXTENDED_FLAGS = 7, CRITTER_DATA_MEMBER_SID = 8, CRITTER_DATA_MEMBER_DATA = 9, CRITTER_DATA_MEMBER_HEAD_FID = 10, CRITTER_DATA_MEMBER_BODY_TYPE = 11, } CritterDataMember; typedef enum SceneryDataMember { SCENERY_DATA_MEMBER_PID = 0, SCENERY_DATA_MEMBER_NAME = 1, SCENERY_DATA_MEMBER_DESCRIPTION = 2, SCENERY_DATA_MEMBER_FID = 3, SCENERY_DATA_MEMBER_LIGHT_DISTANCE = 4, SCENERY_DATA_MEMBER_LIGHT_INTENSITY = 5, SCENERY_DATA_MEMBER_FLAGS = 6, SCENERY_DATA_MEMBER_EXTENDED_FLAGS = 7, SCENERY_DATA_MEMBER_SID = 8, SCENERY_DATA_MEMBER_TYPE = 9, SCENERY_DATA_MEMBER_DATA = 10, SCENERY_DATA_MEMBER_MATERIAL = 11, } SceneryDataMember; typedef enum WallDataMember { WALL_DATA_MEMBER_PID = 0, WALL_DATA_MEMBER_NAME = 1, WALL_DATA_MEMBER_DESCRIPTION = 2, WALL_DATA_MEMBER_FID = 3, WALL_DATA_MEMBER_LIGHT_DISTANCE = 4, WALL_DATA_MEMBER_LIGHT_INTENSITY = 5, WALL_DATA_MEMBER_FLAGS = 6, WALL_DATA_MEMBER_EXTENDED_FLAGS = 7, WALL_DATA_MEMBER_SID = 8, WALL_DATA_MEMBER_MATERIAL = 9, } WallDataMember; typedef enum MiscDataMember { MISC_DATA_MEMBER_PID = 0, MISC_DATA_MEMBER_NAME = 1, MISC_DATA_MEMBER_DESCRIPTION = 2, MISC_DATA_MEMBER_FID = 3, MISC_DATA_MEMBER_LIGHT_DISTANCE = 4, MISC_DATA_MEMBER_LIGHT_INTENSITY = 5, MISC_DATA_MEMBER_FLAGS = 6, MISC_DATA_MEMBER_EXTENDED_FLAGS = 7, } MiscDataMember; typedef enum ProtoDataMemberType { PROTO_DATA_MEMBER_TYPE_INT = 1, PROTO_DATA_MEMBER_TYPE_STRING = 2, } ProtoDataMemberType; typedef union ProtoDataMemberValue { int integerValue; char* stringValue; } ProtoDataMemberValue; typedef enum PrototypeMessage { PROTOTYPE_MESSAGE_NAME, PROTOTYPE_MESSAGE_DESCRIPTION, } PrototypeMesage; extern char cd_path_base[]; extern char proto_path_base[]; extern char* mp_perk_code_strs[1 + PERK_COUNT]; extern char* mp_critter_stats_list[2 + STAT_COUNT]; extern MessageList proto_msg_files[6]; extern char* race_type_strs[2]; extern char* scenery_pro_type[6]; extern MessageList proto_main_msg_file; extern char* item_pro_material[8]; extern char* proto_none_str; extern char* body_type_strs[3]; extern char* item_pro_type[7]; extern char* damage_code_strs[7]; extern char* cal_type_strs[19]; extern char** perk_code_strs; extern char** critter_stats_list; void proto_make_path(char* path, int pid); int proto_list_str(int pid, char* proto_path); bool proto_action_can_use(int pid); bool proto_action_can_use_on(int pid); bool proto_action_can_talk_to(int pid); int proto_action_can_pickup(int pid); char* proto_name(int pid); char* proto_description(int pid); int proto_critter_init(Proto* a1, int a2); void clear_pupdate_data(Object* obj); int proto_read_protoUpdateData(Object* obj, File* stream); int proto_write_protoUpdateData(Object* obj, File* stream); int proto_update_gen(Object* obj); int proto_update_init(Object* obj); int proto_dude_update_gender(); int proto_dude_init(const char* path); int proto_data_member(int pid, int member, ProtoDataMemberValue* value); int proto_init(); void proto_reset(); void proto_exit(); int proto_header_load(); int proto_save_pid(int pid); int proto_load_pid(int pid, Proto** out_proto); int proto_find_free_subnode(int type, Proto** out_ptr); void proto_remove_all(); int proto_ptr(int pid, Proto** out_proto); int proto_max_id(int a1); int ResetPlayer(); #endif /* FALLOUT_GAME_PROTO_H_ */ ================================================ FILE: src/game/proto_types.h ================================================ #ifndef PROTO_TYPES_H #define PROTO_TYPES_H // Number of prototypes in prototype extent. #define PROTO_LIST_EXTENT_SIZE 16 // Max number of prototypes of one type to be stored in prototype cache lists. // Once this value is reached the top most proto extent is removed from the // cache list. // // See: // - [sub_4A2108] // - [sub_4A2040] #define PROTO_LIST_MAX_ENTRIES 512 #define WEAPON_TWO_HAND 0x00000200 enum { GENDER_MALE, GENDER_FEMALE, GENDER_COUNT, }; enum { ITEM_TYPE_ARMOR, ITEM_TYPE_CONTAINER, ITEM_TYPE_DRUG, ITEM_TYPE_WEAPON, ITEM_TYPE_AMMO, ITEM_TYPE_MISC, ITEM_TYPE_KEY, ITEM_TYPE_COUNT, }; enum { SCENERY_TYPE_DOOR, SCENERY_TYPE_STAIRS, SCENERY_TYPE_ELEVATOR, SCENERY_TYPE_LADDER_UP, SCENERY_TYPE_LADDER_DOWN, SCENERY_TYPE_GENERIC, SCENERY_TYPE_COUNT, }; enum { MATERIAL_TYPE_GLASS, MATERIAL_TYPE_METAL, MATERIAL_TYPE_PLASTIC, MATERIAL_TYPE_WOOD, MATERIAL_TYPE_DIRT, MATERIAL_TYPE_STONE, MATERIAL_TYPE_CEMENT, MATERIAL_TYPE_LEATHER, MATERIAL_TYPE_COUNT, }; enum { DAMAGE_TYPE_NORMAL, DAMAGE_TYPE_LASER, DAMAGE_TYPE_FIRE, DAMAGE_TYPE_PLASMA, DAMAGE_TYPE_ELECTRICAL, DAMAGE_TYPE_EMP, DAMAGE_TYPE_EXPLOSION, DAMAGE_TYPE_COUNT, }; enum { CALIBER_TYPE_NONE, CALIBER_TYPE_ROCKET, CALIBER_TYPE_FLAMETHROWER_FUEL, CALIBER_TYPE_C_ENERGY_CELL, CALIBER_TYPE_D_ENERGY_CELL, CALIBER_TYPE_223, CALIBER_TYPE_5_MM, CALIBER_TYPE_40_CAL, CALIBER_TYPE_10_MM, CALIBER_TYPE_44_CAL, CALIBER_TYPE_14_MM, CALIBER_TYPE_12_GAUGE, CALIBER_TYPE_9_MM, CALIBER_TYPE_BB, CALIBER_TYPE_45_CAL, CALIBER_TYPE_2_MM, CALIBER_TYPE_4_7_MM_CASELESS, CALIBER_TYPE_NH_NEEDLER, CALIBER_TYPE_7_62, CALIBER_TYPE_COUNT, }; enum { RACE_TYPE_CAUCASIAN, RACE_TYPE_AFRICAN, RACE_TYPE_COUNT, }; enum { BODY_TYPE_BIPED, BODY_TYPE_QUADRUPED, BODY_TYPE_ROBOTIC, BODY_TYPE_COUNT, }; enum { KILL_TYPE_MAN, KILL_TYPE_WOMAN, KILL_TYPE_CHILD, KILL_TYPE_SUPER_MUTANT, KILL_TYPE_GHOUL, KILL_TYPE_BRAHMIN, KILL_TYPE_RADSCORPION, KILL_TYPE_RAT, KILL_TYPE_FLOATER, KILL_TYPE_CENTAUR, KILL_TYPE_ROBOT, KILL_TYPE_DOG, KILL_TYPE_MANTIS, KILL_TYPE_DEATH_CLAW, KILL_TYPE_PLANT, KILL_TYPE_GECKO, KILL_TYPE_ALIEN, KILL_TYPE_GIANT_ANT, KILL_TYPE_BIG_BAD_BOSS, KILL_TYPE_COUNT, }; enum { PROTO_ID_POWER_ARMOR = 3, PROTO_ID_SMALL_ENERGY_CELL = 38, PROTO_ID_MICRO_FUSION_CELL = 39, PROTO_ID_STIMPACK = 40, PROTO_ID_MONEY = 41, PROTO_ID_FIRST_AID_KIT = 47, PROTO_ID_RADAWAY = 48, PROTO_ID_DYNAMITE_I = 51, PROTO_ID_GEIGER_COUNTER_I = 52, PROTO_ID_MENTATS = 53, PROTO_ID_STEALTH_BOY_I = 54, PROTO_ID_MOTION_SENSOR = 59, PROTO_ID_BIG_BOOK_OF_SCIENCE = 73, PROTO_ID_DEANS_ELECTRONICS = 76, PROTO_ID_FLARE = 79, PROTO_ID_FIRST_AID_BOOK = 80, PROTO_ID_PLASTIC_EXPLOSIVES_I = 85, PROTO_ID_SCOUT_HANDBOOK = 86, PROTO_ID_BUFF_OUT = 87, PROTO_ID_DOCTORS_BAG = 91, PROTO_ID_GUNS_AND_BULLETS = 102, PROTO_ID_NUKA_COLA = 106, PROTO_ID_PSYCHO = 110, PROTO_ID_BEER = 124, PROTO_ID_BOOZE = 125, PROTO_ID_SUPER_STIMPACK = 144, PROTO_ID_MOLOTOV_COCKTAIL = 159, PROTO_ID_LIT_FLARE = 205, PROTO_ID_DYNAMITE_II = 206, // armed PROTO_ID_GEIGER_COUNTER_II = 207, PROTO_ID_PLASTIC_EXPLOSIVES_II = 209, // armed PROTO_ID_STEALTH_BOY_II = 210, PROTO_ID_HARDENED_POWER_ARMOR = 232, PROTO_ID_JET = 259, PROTO_ID_JET_ANTIDOTE = 260, PROTO_ID_HEALING_POWDER = 273, PROTO_ID_DECK_OF_TRAGIC_CARDS = 304, PROTO_ID_CATS_PAW_ISSUE_5 = 331, PROTO_ID_ADVANCED_POWER_ARMOR = 348, PROTO_ID_ADVANCED_POWER_ARMOR_MK_II = 349, PROTO_ID_SHIV = 383, PROTO_ID_SOLAR_SCORCHER = 390, PROTO_ID_SUPER_CATTLE_PROD = 399, PROTO_ID_MEGA_POWER_FIST = 407, PROTO_ID_FIELD_MEDIC_FIRST_AID_KIT = 408, PROTO_ID_PARAMEDICS_BAG = 409, PROTO_ID_RAMIREZ_BOX_CLOSED = 431, PROTO_ID_MIRRORED_SHADES = 433, PROTO_ID_RAIDERS_MAP = 444, PROTO_ID_CAR_TRUNK = 455, PROTO_ID_PIP_BOY_LINGUAL_ENHANCER = 499, PROTO_ID_PIP_BOY_MEDICAL_ENHANCER = 516, PROTO_ID_SURVEY_MAP = 523, }; #define PROTO_ID_0x1000098 0x1000098 #define PROTO_ID_0x10001E0 0x10001E0 #define PROTO_ID_0x2000031 0x2000031 #define PROTO_ID_0x2000158 0x2000158 #define PROTO_ID_CAR 0x20003F1 #define PROTO_ID_0x200050D 0x200050D #define PROTO_ID_0x2000099 0x2000099 #define PROTO_ID_0x20001A5 0x20001A5 #define PROTO_ID_0x20001D6 0x20001D6 #define PROTO_ID_0x20001EB 0x20001EB #define FID_0x20001F5 0x20001F5 // first exit grid #define PROTO_ID_0x5000010 0x5000010 // last exit grid #define PROTO_ID_0x5000017 0x5000017 typedef enum ItemProtoFlags { ItemProtoFlags_0x08 = 0x08, ItemProtoFlags_0x10 = 0x10, ItemProtoFlags_0x1000 = 0x1000, ItemProtoFlags_0x8000 = 0x8000, ItemProtoFlags_0x20000000 = 0x20000000, ItemProtoFlags_0x80000000 = 0x80000000, } ItemProtoFlags; typedef enum ItemProtoExtendedFlags { ItemProtoExtendedFlags_BigGun = 0x0100, ItemProtoExtendedFlags_IsTwoHanded = 0x0200, ItemProtoExtendedFlags_0x0800 = 0x0800, ItemProtoExtendedFlags_0x1000 = 0x1000, ItemProtoExtendedFlags_0x2000 = 0x2000, ItemProtoExtendedFlags_0x8000 = 0x8000, // This flag is used on weapons to indicate that's an natural (integral) // part of it's owner, for example Claw, or Robot's Rocket Launcher. Items // with this flag on do count toward total weight and cannot be dropped. ItemProtoExtendedFlags_NaturalWeapon = 0x08000000, } ItemProtoExtendedFlags; typedef struct { int armorClass; // d.ac int damageResistance[7]; // d.dam_resist int damageThreshold[7]; // d.dam_thresh int perk; // d.perk int maleFid; // d.male_fid int femaleFid; // d.female_fid } ProtoItemArmorData; typedef struct { int maxSize; // d.max_size int openFlags; // d.open_flags } ProtoItemContainerData; typedef struct { int stat[3]; // d.stat int amount[3]; // d.amount int duration1; // d.duration1 int amount1[3]; // d.amount1 int duration2; // d.duration2 int amount2[3]; // d.amount2 int addictionChance; // d.addiction_chance int withdrawalEffect; // d.withdrawal_effect int withdrawalOnset; // d.withdrawal_onset } ProtoItemDrugData; typedef struct { int animationCode; // d.animation_code int minDamage; // d.min_damage int maxDamage; // d.max_damage int damageType; // d.dt int maxRange1; // d.max_range1 int maxRange2; // d.max_range2 int projectilePid; // d.proj_pid int minStrength; // d.min_st int actionPointCost1; // d.mp_cost1 int actionPointCost2; // d.mp_cost2 int criticalFailureType; // d.crit_fail_table int perk; // d.perk int rounds; // d.rounds int caliber; // d.caliber int ammoTypePid; // d.ammo_type_pid int ammoCapacity; // d.max_ammo unsigned char soundCode; // d.sound_id } ProtoItemWeaponData; typedef struct { int caliber; // d.caliber int quantity; // d.quantity int armorClassModifier; // d.ac_adjust int damageResistanceModifier; // d.dr_adjust int damageMultiplier; // d.dam_mult int damageDivisor; // d.dam_div } ProtoItemAmmoData; typedef struct { int powerTypePid; // d.power_type_pid int powerType; // d.power_type int charges; // d.charges } ProtoItemMiscData; typedef struct { int keyCode; // d.key_code } ProtoItemKeyData; typedef struct ItemProtoData { union { struct { int field_0; int field_4; int field_8; // max charges int field_C; int field_10; int field_14; int field_18; } unknown; ProtoItemArmorData armor; ProtoItemContainerData container; ProtoItemDrugData drug; ProtoItemWeaponData weapon; ProtoItemAmmoData ammo; ProtoItemMiscData misc; ProtoItemKeyData key; }; } ItemProtoData; typedef struct ItemProto { int pid; // pid int messageId; // message_num int fid; // fid int lightDistance; // light_distance int lightIntensity; // light_intensity int flags; // flags int extendedFlags; // flags_ext int sid; // sid int type; // type ItemProtoData data; // d int material; // material int size; // size int weight; // weight int cost; // cost int inventoryFid; // inv_fid unsigned char field_80; } ItemProto; static_assert(sizeof(ItemProto) == 0x84, "wrong size"); typedef struct CritterProtoData { int flags; // d.flags int baseStats[35]; // d.stat_base int bonusStats[35]; // d.stat_bonus int skills[18]; // d.stat_points int bodyType; // d.body int experience; int killType; // Looks like this is the "native" damage type when critter is unarmed. int damageType; } CritterProtoData; typedef struct CritterProto { int pid; // pid int messageId; // message_num int fid; // fid int lightDistance; // light_distance int lightIntensity; // light_intensity int flags; // flags int extendedFlags; // flags_ext int sid; // sid CritterProtoData data; // d int headFid; // head_fid int aiPacket; // ai_packet int team; // team_num } CritterProto; static_assert(sizeof(CritterProto) == 0x1A0, "wrong size"); typedef struct { int openFlags; // d.open_flags int keyCode; // d.key_code } SceneryProtoDoorData; typedef struct { int field_0; // d.lower_tile int field_4; // d.upper_tile } SceneryProtoStairsData; typedef struct { int type; int level; } SceneryProtoElevatorData; typedef struct { int field_0; } SceneryProtoLadderData; typedef struct { int field_0; } SceneryProtoGenericData; typedef struct SceneryProtoData { union { SceneryProtoDoorData door; SceneryProtoStairsData stairs; SceneryProtoElevatorData elevator; SceneryProtoLadderData ladder; SceneryProtoGenericData generic; }; } SceneryProtoData; typedef struct SceneryProto { int pid; // id int messageId; // message_num int fid; // fid int lightDistance; // light_distance int lightIntensity; // light_intensity int flags; // flags int extendedFlags; // flags_ext int sid; // sid int type; // type SceneryProtoData data; int field_2C; // material int field_30; // unsigned char field_34; } SceneryProto; static_assert(sizeof(SceneryProto) == 0x38, "wrong size"); typedef struct WallProto { int pid; // id int messageId; // message_num int fid; // fid int lightDistance; // light_distance int lightIntensity; // light_intensity int flags; // flags int extendedFlags; // flags_ext int sid; // sid int material; // material } WallProto; static_assert(sizeof(WallProto) == 0x24, "wrong size"); typedef struct TileProto { int pid; // id int messageId; // message_num int fid; // fid int flags; // flags int extendedFlags; // flags_ext int sid; // sid int material; // material } TileProto; static_assert(sizeof(TileProto) == 0x1C, "wrong size"); typedef struct MiscProto { int pid; // id int messageId; // message_num int fid; // fid int lightDistance; // light_distance int lightIntensity; // light_intensity int flags; // flags int extendedFlags; // flags_ext } MiscProto; static_assert(sizeof(MiscProto) == 0x1C, "wrong size"); typedef union Proto { struct { int pid; // pid int messageId; // message_num int fid; // fid // TODO: Move to NonTile props? int lightDistance; int lightIntensity; int flags; int extendedFlags; int sid; }; ItemProto item; CritterProto critter; SceneryProto scenery; WallProto wall; TileProto tile; MiscProto misc; } Proto; typedef struct ProtoListExtent { Proto* proto[PROTO_LIST_EXTENT_SIZE]; // Number of protos in the extent int length; struct ProtoListExtent* next; } ProtoListExtent; typedef struct ProtoList { ProtoListExtent* head; ProtoListExtent* tail; // Number of extents in the list. int length; // Number of lines in proto/{type}/{type}.lst. int max_entries_num; } ProtoList; #endif /* PROTO_TYPES_H */ ================================================ FILE: src/game/queue.c ================================================ #include "game/queue.h" #include "game/actions.h" #include "game/critter.h" #include "game/display.h" #include "game/game.h" #include "game/gsound.h" #include "game/item.h" #include "game/map.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/protinst.h" #include "game/scripts.h" typedef struct QueueListNode { // TODO: Make unsigned. int time; int type; Object* owner; void* data; struct QueueListNode* next; } QueueListNode; static int queue_destroy(Object* obj, void* data); static int queue_explode(Object* obj, void* data); static int queue_explode_exit(Object* obj, void* data); static int queue_do_explosion(Object* obj, bool a2); static int queue_premature(Object* obj, void* data); // Last queue list node found during [queue_find_first] and // [queue_find_next] calls. // // 0x51C690 static QueueListNode* tmpQNode = NULL; // 0x6648C0 static QueueListNode* queue; // 0x51C540 EventTypeDescription q_func[EVENT_TYPE_COUNT] = { { item_d_process, mem_free, item_d_load, item_d_save, true, item_d_clear }, { critter_wake_up, NULL, NULL, NULL, true, critter_wake_clear }, { item_wd_process, mem_free, item_wd_load, item_wd_save, true, item_wd_clear }, { script_q_process, mem_free, script_q_load, script_q_save, true, NULL }, { gtime_q_process, NULL, NULL, NULL, true, NULL }, { critter_check_poison, NULL, NULL, NULL, false, NULL }, { critter_process_rads, mem_free, critter_load_rads, critter_save_rads, false, NULL }, { queue_destroy, NULL, NULL, NULL, true, queue_destroy }, { queue_explode, NULL, NULL, NULL, true, queue_explode_exit }, { item_m_trickle, NULL, NULL, NULL, true, item_m_turn_off_from_queue }, { critter_sneak_check, NULL, NULL, NULL, true, critter_sneak_clear }, { queue_premature, NULL, NULL, NULL, true, queue_explode_exit }, { scr_map_q_process, NULL, NULL, NULL, true, NULL }, { gsound_sfx_q_process, mem_free, NULL, NULL, true, NULL }, }; // 0x4A2320 void queue_init() { queue = NULL; } // NOTE: Uncollapsed 0x4A2330. // // 0x4A2330 int queue_reset() { queue_clear(); return 0; } // NOTE: Uncollapsed 0x4A2330. // // 0x4A2330 int queue_exit() { queue_clear(); return 0; } // 0x4A2338 int queue_load(File* stream) { int count; if (db_freadInt(stream, &count) == -1) { return -1; } QueueListNode* oldListHead = queue; queue = NULL; QueueListNode** nextPtr = &queue; int rc = 0; for (int index = 0; index < count; index += 1) { QueueListNode* queueListNode = (QueueListNode*)mem_malloc(sizeof(*queueListNode)); if (queueListNode == NULL) { rc = -1; break; } if (db_freadInt(stream, &(queueListNode->time)) == -1) { mem_free(queueListNode); rc = -1; break; } if (db_freadInt(stream, &(queueListNode->type)) == -1) { mem_free(queueListNode); rc = -1; break; } int objectId; if (db_freadInt(stream, &objectId) == -1) { mem_free(queueListNode); rc = -1; break; } Object* obj; if (objectId == -2) { obj = NULL; } else { obj = obj_find_first(); while (obj != NULL) { obj = inven_find_id(obj, objectId); if (obj != NULL) { break; } obj = obj_find_next(); } } queueListNode->owner = obj; EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]); if (eventTypeDescription->readProc != NULL) { if (eventTypeDescription->readProc(stream, &(queueListNode->data)) == -1) { mem_free(queueListNode); rc = -1; break; } } else { queueListNode->data = NULL; } queueListNode->next = NULL; *nextPtr = queueListNode; nextPtr = &(queueListNode->next); } if (rc == -1) { while (queue != NULL) { QueueListNode* next = queue->next; EventTypeDescription* eventTypeDescription = &(q_func[queue->type]); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(queue->data); } mem_free(queue); queue = next; } } if (oldListHead != NULL) { QueueListNode** v13 = &queue; QueueListNode* v15; do { while (true) { QueueListNode* v14 = *v13; if (v14 == NULL) { break; } if (v14->time > oldListHead->time) { break; } v13 = &(v14->next); } v15 = oldListHead->next; oldListHead->next = *v13; *v13 = oldListHead; oldListHead = v15; } while (v15 != NULL); } return rc; } // 0x4A24E0 int queue_save(File* stream) { QueueListNode* queueListNode; int count = 0; queueListNode = queue; while (queueListNode != NULL) { count += 1; queueListNode = queueListNode->next; } if (db_fwriteInt(stream, count) == -1) { return -1; } queueListNode = queue; while (queueListNode != NULL) { Object* object = queueListNode->owner; int objectId = object != NULL ? object->id : -2; if (db_fwriteInt(stream, queueListNode->time) == -1) { return -1; } if (db_fwriteInt(stream, queueListNode->type) == -1) { return -1; } if (db_fwriteInt(stream, objectId) == -1) { return -1; } EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]); if (eventTypeDescription->writeProc != NULL) { if (eventTypeDescription->writeProc(stream, queueListNode->data) == -1) { return -1; } } queueListNode = queueListNode->next; } return 0; } // 0x4A258C int queue_add(int delay, Object* obj, void* data, int eventType) { QueueListNode* newQueueListNode = (QueueListNode*)mem_malloc(sizeof(QueueListNode)); if (newQueueListNode == NULL) { return -1; } int v1 = game_time(); int v2 = v1 + delay; newQueueListNode->time = v2; newQueueListNode->type = eventType; newQueueListNode->owner = obj; newQueueListNode->data = data; if (obj != NULL) { obj->flags |= OBJECT_USED; } QueueListNode** v3 = &queue; if (queue != NULL) { QueueListNode* v4; do { v4 = *v3; if (v2 < v4->time) { break; } v3 = &(v4->next); } while (v4->next != NULL); } newQueueListNode->next = *v3; *v3 = newQueueListNode; return 0; } // 0x4A25F4 int queue_remove(Object* owner) { QueueListNode* queueListNode = queue; QueueListNode** queueListNodePtr = &queue; while (queueListNode) { if (queueListNode->owner == owner) { QueueListNode* temp = queueListNode; queueListNode = queueListNode->next; *queueListNodePtr = queueListNode; EventTypeDescription* eventTypeDescription = &(q_func[temp->type]); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(temp->data); } mem_free(temp); } else { queueListNodePtr = &(queueListNode->next); queueListNode = queueListNode->next; } } return 0; } // 0x4A264C int queue_remove_this(Object* owner, int eventType) { QueueListNode* queueListNode = queue; QueueListNode** queueListNodePtr = &queue; while (queueListNode) { if (queueListNode->owner == owner && queueListNode->type == eventType) { QueueListNode* temp = queueListNode; queueListNode = queueListNode->next; *queueListNodePtr = queueListNode; EventTypeDescription* eventTypeDescription = &(q_func[temp->type]); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(temp->data); } mem_free(temp); } else { queueListNodePtr = &(queueListNode->next); queueListNode = queueListNode->next; } } return 0; } // Returns true if there is at least one event of given type scheduled. // // 0x4A26A8 bool queue_find(Object* owner, int eventType) { QueueListNode* queueListEvent = queue; while (queueListEvent != NULL) { if (owner == queueListEvent->owner && eventType == queueListEvent->type) { return true; } queueListEvent = queueListEvent->next; } return false; } // 0x4A26D0 int queue_process() { int time = game_time(); int v1 = 0; while (queue != NULL) { QueueListNode* queueListNode = queue; if (time < queueListNode->time || v1 != 0) { break; } queue = queueListNode->next; EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]); v1 = eventTypeDescription->handlerProc(queueListNode->owner, queueListNode->data); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(queueListNode->data); } mem_free(queueListNode); } return v1; } // 0x4A2748 void queue_clear() { QueueListNode* queueListNode = queue; while (queueListNode != NULL) { QueueListNode* next = queueListNode->next; EventTypeDescription* eventTypeDescription = &(q_func[queueListNode->type]); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(queueListNode->data); } mem_free(queueListNode); queueListNode = next; } queue = NULL; } // 0x4A2790 void queue_clear_type(int eventType, QueueEventHandler* fn) { QueueListNode** ptr = &queue; QueueListNode* curr = *ptr; while (curr != NULL) { if (eventType == curr->type) { QueueListNode* tmp = curr; *ptr = curr->next; curr = *ptr; if (fn != NULL && fn(tmp->owner, tmp->data) != 1) { *ptr = tmp; ptr = &(tmp->next); } else { EventTypeDescription* eventTypeDescription = &(q_func[tmp->type]); if (eventTypeDescription->freeProc != NULL) { eventTypeDescription->freeProc(tmp->data); } mem_free(tmp); } } else { ptr = &(curr->next); curr = *ptr; } } } // TODO: Make unsigned. // // 0x4A2808 int queue_next_time() { if (queue == NULL) { return 0; } return queue->time; } // 0x4A281C static int queue_destroy(Object* obj, void* data) { obj_destroy(obj); return 1; } // 0x4A2828 static int queue_explode(Object* obj, void* data) { return queue_do_explosion(obj, true); } // 0x4A2830 static int queue_explode_exit(Object* obj, void* data) { return queue_do_explosion(obj, false); } // 0x4A2834 static int queue_do_explosion(Object* explosive, bool a2) { int tile; int elevation; Object* owner = obj_top_environment(explosive); if (owner) { tile = owner->tile; elevation = owner->elevation; } else { tile = explosive->tile; elevation = explosive->elevation; } int maxDamage; int minDamage; if (explosive->pid == PROTO_ID_DYNAMITE_I || explosive->pid == PROTO_ID_DYNAMITE_II) { // Dynamite minDamage = 30; maxDamage = 50; } else { // Plastic explosive minDamage = 40; maxDamage = 80; } // FIXME: I guess this is a little bit wrong, dude can never be null, I // guess it needs to check if owner is dude. if (obj_dude != NULL) { if (perkHasRank(obj_dude, PERK_DEMOLITION_EXPERT)) { maxDamage += 10; minDamage += 10; } } if (action_explode(tile, elevation, minDamage, maxDamage, obj_dude, a2) == -2) { queue_add(50, explosive, NULL, EVENT_TYPE_EXPLOSION); } else { obj_destroy(explosive); } return 1; } // 0x4A28E4 static int queue_premature(Object* obj, void* data) { MessageListItem msg; // Due to your inept handling, the explosive detonates prematurely. msg.num = 4000; if (message_search(&misc_message_file, &msg)) { display_print(msg.text); } return queue_do_explosion(obj, true); } // 0x4A2920 void queue_leaving_map() { for (int eventType = 0; eventType < EVENT_TYPE_COUNT; eventType++) { EventTypeDescription* eventTypeDescription = &(q_func[eventType]); if (eventTypeDescription->field_10) { queue_clear_type(eventType, eventTypeDescription->field_14); } } } // 0x4A294C bool queue_is_empty() { return queue == NULL; } // 0x4A295C void* queue_find_first(Object* owner, int eventType) { QueueListNode* queueListNode = queue; while (queueListNode != NULL) { if (owner == queueListNode->owner && eventType == queueListNode->type) { tmpQNode = queueListNode; return queueListNode->data; } queueListNode = queueListNode->next; } tmpQNode = NULL; return NULL; } // 0x4A2994 void* queue_find_next(Object* owner, int eventType) { if (tmpQNode != NULL) { QueueListNode* queueListNode = tmpQNode->next; while (queueListNode != NULL) { if (owner == queueListNode->owner && eventType == queueListNode->type) { tmpQNode = queueListNode; return queueListNode->data; } queueListNode = queueListNode->next; } } tmpQNode = NULL; return NULL; } ================================================ FILE: src/game/queue.h ================================================ #ifndef FALLOUT_GAME_QUEUE_H_ #define FALLOUT_GAME_QUEUE_H_ #include #include "plib/db/db.h" #include "game/object_types.h" typedef enum EventType { EVENT_TYPE_DRUG = 0, EVENT_TYPE_KNOCKOUT = 1, EVENT_TYPE_WITHDRAWAL = 2, EVENT_TYPE_SCRIPT = 3, EVENT_TYPE_GAME_TIME = 4, EVENT_TYPE_POISON = 5, EVENT_TYPE_RADIATION = 6, EVENT_TYPE_FLARE = 7, EVENT_TYPE_EXPLOSION = 8, EVENT_TYPE_ITEM_TRICKLE = 9, EVENT_TYPE_SNEAK = 10, EVENT_TYPE_EXPLOSION_FAILURE = 11, EVENT_TYPE_MAP_UPDATE_EVENT = 12, EVENT_TYPE_GSOUND_SFX_EVENT = 13, EVENT_TYPE_COUNT, } EventType; typedef struct DrugEffectEvent { int drugPid; int stats[3]; int modifiers[3]; } DrugEffectEvent; typedef struct WithdrawalEvent { int field_0; int pid; int perk; } WithdrawalEvent; typedef struct ScriptEvent { int sid; int fixedParam; } ScriptEvent; typedef struct RadiationEvent { int radiationLevel; int isHealing; } RadiationEvent; typedef struct AmbientSoundEffectEvent { int ambientSoundEffectIndex; } AmbientSoundEffectEvent; typedef int QueueEventHandler(Object* owner, void* data); typedef void QueueEventDataFreeProc(void* data); typedef int QueueEventDataReadProc(File* stream, void** dataPtr); typedef int QueueEventDataWriteProc(File* stream, void* data); typedef struct EventTypeDescription { QueueEventHandler* handlerProc; QueueEventDataFreeProc* freeProc; QueueEventDataReadProc* readProc; QueueEventDataWriteProc* writeProc; bool field_10; QueueEventHandler* field_14; } EventTypeDescription; extern EventTypeDescription q_func[EVENT_TYPE_COUNT]; void queue_init(); int queue_reset(); int queue_exit(); int queue_load(File* stream); int queue_save(File* stream); int queue_add(int delay, Object* owner, void* data, int eventType); int queue_remove(Object* owner); int queue_remove_this(Object* owner, int eventType); bool queue_find(Object* owner, int eventType); int queue_process(); void queue_clear(); void queue_clear_type(int eventType, QueueEventHandler* fn); int queue_next_time(); void queue_leaving_map(); bool queue_is_empty(); void* queue_find_first(Object* owner, int eventType); void* queue_find_next(Object* owner, int eventType); #endif /* FALLOUT_GAME_QUEUE_H_ */ ================================================ FILE: src/game/reaction.c ================================================ #include "game/reaction.h" #include "game/scripts.h" // 0x4A29D0 int reaction_set(Object* critter, int value) { scr_set_local_var(critter->sid, 0, value); return 0; } // NOTE: Unused. // // 0x4A29E4 int level_to_reaction() { return 0; } // 0x4A29E8 int reaction_lookup_internal(int a1) { if (a1 > 10) { return NPC_REACTION_GOOD; } else if (a1 > -10) { return NPC_REACTION_NEUTRAL; } else if (a1 > -25) { return NPC_REACTION_BAD; } else if (a1 > -50) { return NPC_REACTION_BAD; } else if (a1 > -75) { return NPC_REACTION_BAD; } else { return NPC_REACTION_BAD; } } // NOTE: Unused. // // 0x4A2A2C int reaction_to_level_internal(int sid, int reaction) { int level; if (reaction <= -75) { level = -4; } else if (reaction <= -50) { level = -3; } else if (reaction <= -25) { level = -2; } else if (reaction <= -10) { level = -1; } else if (reaction <= 10) { level = 0; } else if (reaction <= 25) { level = 1; } else if (reaction <= 50) { level = 2; } else if (reaction <= 75) { level = 3; } else { level = 4; } scr_set_local_var(sid, 1, level); return reaction_lookup_internal(reaction); } // NOTE: From OS X binary. int reaction_to_level(int a1) { return reaction_lookup_internal(a1); } // 0x4A2B28 int reaction_get(Object* critter) { int reactionValue; if (scr_get_local_var(critter->sid, 0, &reactionValue) == -1) { return -1; } return reactionValue; } // NOTE: From OS X binary. int reaction_roll() { return 0; } // TODO: Accepts 3 parameters, check `op_reaction_influence`. // // 0x4A29F0 int reaction_influence() { return 0; } ================================================ FILE: src/game/reaction.h ================================================ #ifndef FALLOUT_GAME_REACTION_H_ #define FALLOUT_GAME_REACTION_H_ #include "game/object_types.h" typedef enum NpcReaction { NPC_REACTION_BAD, NPC_REACTION_NEUTRAL, NPC_REACTION_GOOD, } NpcReaction; int reaction_set(Object* critter, int value); int level_to_reaction(); int reaction_lookup_internal(int a1); int reaction_to_level_internal(int sid, int reaction); int reaction_to_level(int a1); int reaction_get(Object* critter); int reaction_roll(); int reaction_influence(); #endif /* FALLOUT_GAME_REACTION_H_ */ ================================================ FILE: src/game/roll.c ================================================ #include "game/roll.h" #include #include // clang-format off #define WIN32_LEAN_AND_MEAN #include #include // clang-format on #include "plib/gnw/debug.h" #include "game/scripts.h" static int ran1(int max); static void init_random(); static int random_seed(); static void seed_generator(int seed); static unsigned int timer_read(); static void check_chi_squared(); // 0x51C694 static int iy = 0; // 0x6648D0 static int iv[32]; // 0x664950 static int idum; // 0x4A2FE0 void roll_init() { // NOTE: Uninline. init_random(); check_chi_squared(); } // NOTE: Uncollapsed 0x4A2FFC. int roll_reset() { return 0; } // NOTE: Uncollapsed 0x4A2FFC. int roll_exit() { return 0; } // NOTE: Uncollapsed 0x4A2FFC. int roll_save(File* stream) { return 0; } // NOTE: Uncollapsed 0x4A2FFC. int roll_load(File* stream) { return 0; } // Rolls d% against [difficulty]. // // 0x4A3000 int roll_check(int difficulty, int criticalSuccessModifier, int* howMuchPtr) { int delta = difficulty - roll_random(1, 100); int result = roll_check_critical(delta, criticalSuccessModifier); if (howMuchPtr != NULL) { *howMuchPtr = delta; } return result; } // Translates raw d% result into [Roll] constants, possibly upgrading to // criticals (starting from day 2). // // 0x4A3030 int roll_check_critical(int delta, int criticalSuccessModifier) { int gameTime = game_time(); int roll; if (delta < 0) { roll = ROLL_FAILURE; if ((gameTime / GAME_TIME_TICKS_PER_DAY) >= 1) { // 10% to become critical failure. if (roll_random(1, 100) <= -delta / 10) { roll = ROLL_CRITICAL_FAILURE; } } } else { roll = ROLL_SUCCESS; if ((gameTime / GAME_TIME_TICKS_PER_DAY) >= 1) { // 10% + modifier to become critical success. if (roll_random(1, 100) <= delta / 10 + criticalSuccessModifier) { roll = ROLL_CRITICAL_SUCCESS; } } } return roll; } // 0x4A30C0 int roll_random(int min, int max) { int result; if (min <= max) { result = min + ran1(max - min + 1); } else { result = max + ran1(min - max + 1); } if (result < min || result > max) { debug_printf("Random number %d is not in range %d to %d", result, min, max); result = min; } return result; } // 0x4A30FC static int ran1(int max) { int v1 = 16807 * (idum % 127773) - 2836 * (idum / 127773); if (v1 < 0) { v1 += 0x7FFFFFFF; } if (v1 < 0) { v1 += 0x7FFFFFFF; } int v2 = iy & 0x1F; int v3 = iv[v2]; iv[v2] = v1; iy = v3; idum = v1; return v3 % max; } // NOTE: Inlined. // // 0x4A3174 static void init_random() { srand(timer_read()); seed_generator(random_seed()); } // 0x4A31A0 void roll_set_seed(int seed) { if (seed == -1) { // NOTE: Uninline. seed = random_seed(); } seed_generator(seed); } // 0x4A31C4 static int random_seed() { int high = rand() << 16; int low = rand(); return (high + low) & INT_MAX; } // 0x4A31E0 static void seed_generator(int seed) { int num = seed; if (num < 1) { num = 1; } for (int index = 40; index > 0; index--) { num = 16807 * (num % 127773) - 2836 * (num / 127773); if (num < 0) { num &= INT_MAX; } if (index < 32) { iv[index] = num; } } iy = iv[0]; idum = num; } // Provides seed for random number generator. // // 0x4A3258 static unsigned int timer_read() { return timeGetTime(); } // 0x4A3264 static void check_chi_squared() { int results[25]; for (int index = 0; index < 25; index++) { results[index] = 0; } for (int attempt = 0; attempt < 100000; attempt++) { int value = roll_random(1, 25); if (value - 1 < 0) { debug_printf("I made a negative number %d\n", value - 1); } results[value - 1]++; } double v1 = 0.0; for (int index = 0; index < 25; index++) { double v2 = ((double)results[index] - 4000.0) * ((double)results[index] - 4000.0) / 4000.0; v1 += v2; } debug_printf("Chi squared is %f, P = %f at 0.05\n", v1, 4000.0); if (v1 < 36.42) { debug_printf("Sequence is random, 95%% confidence.\n"); } else { debug_printf("Warning! Sequence is not random, 95%% confidence.\n"); } } ================================================ FILE: src/game/roll.h ================================================ #ifndef FALLOUT_GAME_ROLL_H_ #define FALLOUT_GAME_ROLL_H_ #include "plib/db/db.h" typedef enum Roll { ROLL_CRITICAL_FAILURE, ROLL_FAILURE, ROLL_SUCCESS, ROLL_CRITICAL_SUCCESS, } Roll; void roll_init(); int roll_reset(); int roll_exit(); int roll_save(File* stream); int roll_load(File* stream); int roll_check(int difficulty, int criticalSuccessModifier, int* howMuchPtr); int roll_check_critical(int delta, int criticalSuccessModifier); int roll_random(int min, int max); void roll_set_seed(int seed); #endif /* FALLOUT_GAME_ROLL_H_ */ ================================================ FILE: src/game/scripts.c ================================================ #include "game/scripts.h" #include #include #include #include #include "int/window.h" #include "game/actions.h" #include "game/automap.h" #include "game/combat.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "int/dialog.h" #include "game/elevator.h" #include "game/endgame.h" #include "int/export.h" #include "game/game.h" #include "game/gdialog.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/proto.h" #include "game/protinst.h" #include "game/queue.h" #include "game/tile.h" #include "plib/gnw/gnw.h" #include "plib/gnw/intrface.h" #include "game/worldmap.h" #define SCRIPT_LIST_EXTENT_SIZE 16 typedef struct ScriptListExtent { Script scripts[SCRIPT_LIST_EXTENT_SIZE]; // Number of scripts in the extent int length; struct ScriptListExtent* next; } ScriptListExtent; static_assert(sizeof(ScriptListExtent) == 0xE08, "wrong size"); typedef struct ScriptList { ScriptListExtent* head; ScriptListExtent* tail; // Number of extents in the script list. int length; int nextScriptId; } ScriptList; static_assert(sizeof(ScriptList) == 0x10, "wrong size"); typedef struct ScriptState { unsigned int requests; STRUCT_664980 combatState1; STRUCT_664980 combatState2; int elevatorType; int elevatorLevel; int explosionTile; int explosionElevation; int explosionMinDamage; int explosionMaxDamage; Object* dialogTarget; Object* lootingBy; Object* lootingFrom; Object* stealingBy; Object* stealingFrom; } ScriptState; static void doBkProcesses(); static void script_chk_critters(); static void script_chk_timed_events(); static int scr_build_lookup_table(Script* scr); static int scrInitListInfo(); static int scrExitListInfo(); static int scr_index_to_name(int scriptIndex, char* name); static int scr_header_load(); static int scr_write_ScriptSubNode(Script* scr, File* stream); static int scr_write_ScriptNode(ScriptListExtent* a1, File* stream); static int scr_read_ScriptSubNode(Script* scr, File* stream); static int scr_read_ScriptNode(ScriptListExtent* a1, File* stream); static int scr_new_id(int scriptType); static void scrExecMapProcScripts(int a1); // Number of lines in scripts.lst // // 0x51C6AC int num_script_indexes = 0; // 0x51C6B0 static int scr_find_first_idx = 0; // 0x51C6B4 static ScriptListExtent* scr_find_first_ptr = NULL; // 0x51C6B8 static int scr_find_first_elev = 0; // 0x51C6BC static bool scrSpatialsEnabled = true; // 0x51C6C0 static ScriptList scriptlists[SCRIPT_TYPE_COUNT]; // 0x51C710 static char script_path_base[] = "scripts\\"; // 0x51C714 static bool script_engine_running = false; // 0x51C718 static int script_engine_run_critters = 0; // 0x51C71C static int script_engine_game_mode = 0; // Game time in ticks (1/10 second). // // 0x51C720 static int fallout_game_time = 302400; // 0x51C724 static int days_in_month[12] = { 31, // Jan 28, // Feb 31, // Mar 30, // Apr 31, // May 30, // Jun 31, // Jul 31, // Aug 30, // Sep 31, // Oct 30, // Nov 31, // Dec }; // 0x51C758 static const char* procTableStrs[SCRIPT_PROC_COUNT] = { "no_p_proc", "start", "spatial_p_proc", "description_p_proc", "pickup_p_proc", "drop_p_proc", "use_p_proc", "use_obj_on_p_proc", "use_skill_on_p_proc", "none_x_bad", "none_x_bad", "talk_p_proc", "critter_p_proc", "combat_p_proc", "damage_p_proc", "map_enter_p_proc", "map_exit_p_proc", "create_p_proc", "destroy_p_proc", "none_x_bad", "none_x_bad", "look_at_p_proc", "timed_event_p_proc", "map_update_p_proc", "push_p_proc", "is_dropping_p_proc", "combat_is_starting_p_proc", "combat_is_over_p_proc", }; // scripts.lst // // 0x51C7C8 static ScriptsListEntry* scriptListInfo = NULL; // 0x51C7CC static int maxScriptNum = 0; // 0x51C7E8 Object* scrQueueTestObj = NULL; // 0x51C7EC int scrQueueTestValue = 0; // 0x664954 static ScriptState scriptState; // 0x6649D4 MessageList script_dialog_msgs[1450]; // scr.msg // // 0x667724 MessageList script_message_file; // TODO: Make unsigned. // // Returns game time in ticks (1/10 second). // // 0x4A3330 int game_time() { return fallout_game_time; } // 0x4A3338 void game_time_date(int* monthPtr, int* dayPtr, int* yearPtr) { int year = (fallout_game_time / GAME_TIME_TICKS_PER_DAY + 24) / 365 + 2241; int month = 6; int day = (fallout_game_time / GAME_TIME_TICKS_PER_DAY + 24) % 365; while (1) { int daysInMonth = days_in_month[month]; if (day < daysInMonth) { break; } month++; day -= daysInMonth; if (month == 12) { year++; month = 0; } } if (dayPtr != NULL) { *dayPtr = day + 1; } if (monthPtr != NULL) { *monthPtr = month + 1; } if (yearPtr != NULL) { *yearPtr = year; } } // Returns game hour/minute in military format (hhmm). // // Examples: // - 8:00 A.M. -> 800 // - 3:00 P.M. -> 1500 // - 11:59 P.M. -> 2359 // // game_time_hour // 0x4A33C8 int game_time_hour() { return 100 * ((fallout_game_time / 600) / 60 % 24) + (fallout_game_time / 600) % 60; } // Returns time string (h:mm) // // 0x4A3420 char* game_time_hour_str() { // 0x66772C static char hour_str[7]; sprintf(hour_str, "%d:%02d", (fallout_game_time / 600) / 60 % 24, (fallout_game_time / 600) % 60); return hour_str; } // TODO: Make unsigned. // // 0x4A347C void gameTimeSetTime(int time) { if (time == 0) { time = 1; } fallout_game_time = time; } // 0x4A34CC void inc_game_time(int ticks) { fallout_game_time += ticks; int v1 = 0; unsigned int year = fallout_game_time / GAME_TIME_TICKS_PER_YEAR; if (year >= 13) { endgameSetupDeathEnding(ENDGAME_DEATH_ENDING_REASON_TIMEOUT); game_user_wants_to_quit = 2; } // FIXME: This condition will never be true. if (v1) { gtime_q_process(NULL, NULL); } } // 0x4A3518 void inc_game_time_in_seconds(int seconds) { // NOTE: Uninline. inc_game_time(seconds * 10); } // 0x4A3570 int gtime_q_add() { int v1 = 10 * (60 * (60 - (fallout_game_time / 600) % 60 - 1) + 3600 * (24 - (fallout_game_time / 600) / 60 % 24 - 1) + 60); if (queue_add(v1, NULL, NULL, EVENT_TYPE_GAME_TIME) == -1) { return -1; } if (map_data.name[0] != '\0') { if (queue_add(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) == -1) { return -1; } } return 0; } // 0x4A3620 int gtime_q_process(Object* obj, void* data) { int movie_index; int v4; movie_index = -1; debug_printf("\nQUEUE PROCESS: Midnight!"); if (gmovieIsPlaying()) { return 0; } obj_unjam_all_locks(); if (!gdialogActive()) { scriptsCheckGameEvents(&movie_index, -1); } v4 = critter_check_rads(obj_dude); queue_clear_type(4, 0); gtime_q_add(); if (movie_index != -1) { v4 = 1; } return v4; } // 0x4A3690 int scriptsCheckGameEvents(int* moviePtr, int window) { int movie = -1; int movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC; bool endgame = false; bool adjustRep = false; int day = fallout_game_time / GAME_TIME_TICKS_PER_DAY; if (game_get_global_var(GVAR_ENEMY_ARROYO)) { movie = MOVIE_AFAILED; movieFlags = GAME_MOVIE_FADE_IN | GAME_MOVIE_STOP_MUSIC; endgame = true; } else { if (day >= 360 || game_get_global_var(GVAR_FALLOUT_2) >= 3) { movie = MOVIE_ARTIMER4; if (!gmovie_has_been_played(MOVIE_ARTIMER4)) { adjustRep = true; wmAreaSetVisibleState(CITY_ARROYO, 0, 1); wmAreaSetVisibleState(CITY_DESTROYED_ARROYO, 1, 1); wmAreaMarkVisitedState(CITY_DESTROYED_ARROYO, 2); } } else if (day >= 270 && game_get_global_var(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER3; } else if (day >= 180 && game_get_global_var(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER2; } else if (day >= 90 && game_get_global_var(GVAR_FALLOUT_2) != 3) { adjustRep = true; movie = MOVIE_ARTIMER1; } } if (movie != -1) { if (gmovie_has_been_played(movie)) { movie = -1; } else { if (window != -1) { win_hide(window); } gmovie_play(movie, movieFlags); if (window != -1) { win_show(window); } if (adjustRep) { int rep = game_get_global_var(GVAR_TOWN_REP_ARROYO); game_set_global_var(GVAR_TOWN_REP_ARROYO, rep - 15); } } } if (endgame) { game_user_wants_to_quit = 2; } else { tile_refresh_display(); } if (moviePtr != NULL) { *moviePtr = movie; } return 0; } // 0x4A382C int scr_map_q_process(Object* obj, void* data) { scrExecMapProcScripts(SCRIPT_PROC_MAP_UPDATE); queue_clear_type(EVENT_TYPE_MAP_UPDATE_EVENT, NULL); if (map_data.name[0] == '\0') { return 0; } if (queue_add(600, NULL, NULL, EVENT_TYPE_MAP_UPDATE_EVENT) != -1) { return 0; } return -1; } // 0x4A386C int new_obj_id() { // 0x51C7D4 static int cur_id = 4; Object* ptr; do { cur_id++; ptr = obj_find_first(); while (ptr) { if (cur_id == ptr->id) { break; } ptr = obj_find_next(); } } while (ptr); if (cur_id >= 18000) { debug_printf("\n ERROR: new_obj_id() !!!! Picked PLAYER ID!!!!"); } cur_id++; return cur_id; } // 0x4A390C int scr_find_sid_from_program(Program* program) { for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) { ScriptListExtent* extent = scriptlists[type].head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* script = &(extent->scripts[index]); if (script->program == program) { return script->sid; } } extent = extent->next; } } return -1; } // 0x4A39AC Object* scr_find_obj_from_program(Program* program) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { return NULL; } if (script->owner != NULL) { return script->owner; } if (SID_TYPE(sid) != SCRIPT_TYPE_SPATIAL) { return NULL; } Object* object; int fid = art_id(OBJ_TYPE_INTERFACE, 3, 0, 0, 0); obj_new(&object, fid, -1); obj_turn_off(object, NULL); obj_toggle_flat(object, NULL); object->sid = sid; // NOTE: Redundant, we've already obtained script earlier. Probably // inlining. Script* v1; if (scr_ptr(sid, &v1) == -1) { // FIXME: this is clearly an error, but I guess it's never reached since // we've already obtained script for given sid earlier. return (Object*)-1; } object->id = new_obj_id(); v1->field_1C = object->id; v1->owner = object; for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { Script* spatialScript = scr_find_first_at(elevation); while (spatialScript != NULL) { if (spatialScript == script) { obj_move_to_tile(object, builtTileGetTile(script->sp.built_tile), elevation, NULL); return object; } spatialScript = scr_find_next_at(); } } return object; } // 0x4A3B0C int scr_set_objs(int sid, Object* source, Object* target) { Script* script; if (scr_ptr(sid, &script) == -1) { return -1; } script->source = source; script->target = target; return 0; } // 0x4A3B34 void scr_set_ext_param(int sid, int value) { Script* script; if (scr_ptr(sid, &script) != -1) { script->fixedParam = value; } } // 0x4A3B54 int scr_set_action_num(int sid, int value) { Script* scr; if (scr_ptr(sid, &scr) == -1) { return -1; } scr->actionBeingUsed = value; return 0; } // 0x4A3B74 Program* loadProgram(const char* name) { char path[MAX_PATH]; strcpy(path, cd_path_base); strcat(path, script_path_base); strcat(path, name); strcat(path, ".int"); return allocateProgram(path); } // 0x4A3C2C static void doBkProcesses() { // 0x66774C static bool set; // 0x667748 static int lasttime; if (!set) { lasttime = get_bk_time(); set = 1; } int v0 = get_bk_time(); if (script_engine_running) { lasttime = v0; // NOTE: There is a loop at 0x4A3C64, consisting of one iteration, going // downwards from 1. for (int index = 0; index < 1; index++) { updatePrograms(); } } updateWindows(); if (script_engine_running && script_engine_run_critters) { if (!gdialogActive()) { script_chk_critters(); script_chk_timed_events(); } } } // 0x4A3CA0 static void script_chk_critters() { // 0x51C7DC static int count = 0; if (!gdialogActive() && !isInCombat()) { ScriptList* scriptList; ScriptListExtent* scriptListExtent; int scriptsCount = 0; scriptList = &(scriptlists[SCRIPT_TYPE_CRITTER]); scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { scriptsCount += scriptListExtent->length; scriptListExtent = scriptListExtent->next; } count += 1; if (count >= scriptsCount) { count = 0; } if (count < scriptsCount) { int proc = isInCombat() ? SCRIPT_PROC_COMBAT : SCRIPT_PROC_CRITTER; int extentIndex = count / SCRIPT_LIST_EXTENT_SIZE; int scriptIndex = count % SCRIPT_LIST_EXTENT_SIZE; scriptList = &(scriptlists[SCRIPT_TYPE_CRITTER]); scriptListExtent = scriptList->head; while (scriptListExtent != NULL && extentIndex != 0) { extentIndex -= 1; scriptListExtent = scriptListExtent->next; } if (scriptListExtent != NULL) { Script* script = &(scriptListExtent->scripts[scriptIndex]); exec_script_proc(script->sid, proc); } } } } // TODO: Check. // // 0x4A3D84 static void script_chk_timed_events() { // 0x51C7E0 static int last_time = 0; // 0x51C7E4 static int last_light_time = 0; int v0 = get_bk_time(); int v1 = false; if (!isInCombat()) { v1 = true; } if (game_state() != GAME_STATE_4) { if (elapsed_tocks(v0, last_light_time) >= 30000) { last_light_time = v0; scrExecMapProcScripts(SCRIPT_PROC_MAP_UPDATE); } } else { v1 = false; } if (elapsed_tocks(v0, last_time) >= 100) { last_time = v0; if (!isInCombat()) { fallout_game_time += 1; } v1 = true; } if (v1) { while (!queue_is_empty()) { int time = game_time(); int v2 = queue_next_time(); if (time < v2) { break; } queue_process(); } } } // 0x4A3E30 void scrSetQueueTestVals(Object* a1, int a2) { scrQueueTestObj = a1; scrQueueTestValue = a2; } // 0x4A3E3C int scrQueueRemoveFixed(Object* obj, void* data) { ScriptEvent* scriptEvent = (ScriptEvent*)data; return obj == scrQueueTestObj && scriptEvent->fixedParam == scrQueueTestValue; } // 0x4A3E60 int script_q_add(int sid, int delay, int param) { ScriptEvent* scriptEvent = (ScriptEvent*)mem_malloc(sizeof(*scriptEvent)); if (scriptEvent == NULL) { return -1; } scriptEvent->sid = sid; scriptEvent->fixedParam = param; Script* script; if (scr_ptr(sid, &script) == -1) { mem_free(scriptEvent); return -1; } if (queue_add(delay, script->owner, scriptEvent, EVENT_TYPE_SCRIPT) == -1) { mem_free(scriptEvent); return -1; } return 0; } // 0x4A3EDC int script_q_save(File* stream, void* data) { ScriptEvent* scriptEvent = (ScriptEvent*)data; if (db_fwriteInt(stream, scriptEvent->sid) == -1) return -1; if (db_fwriteInt(stream, scriptEvent->fixedParam) == -1) return -1; return 0; } // 0x4A3F04 int script_q_load(File* stream, void** dataPtr) { ScriptEvent* scriptEvent = (ScriptEvent*)mem_malloc(sizeof(*scriptEvent)); if (scriptEvent == NULL) { return -1; } if (db_freadInt(stream, &(scriptEvent->sid)) == -1) goto err; if (db_freadInt(stream, &(scriptEvent->fixedParam)) == -1) goto err; *dataPtr = scriptEvent; return 0; err: // there is a memory leak in original code, free is not called mem_free(scriptEvent); return -1; } // 0x4A3F4C int script_q_process(Object* obj, void* data) { ScriptEvent* scriptEvent = (ScriptEvent*)data; Script* script; if (scr_ptr(scriptEvent->sid, &script) == -1) { return 0; } script->fixedParam = scriptEvent->fixedParam; exec_script_proc(scriptEvent->sid, SCRIPT_PROC_TIMED); return 0; } // 0x4A3F80 int scripts_clear_state() { scriptState.requests = 0; return 0; } // NOTE: Inlined. // // 0x4A3F90 int scripts_clear_combat_requests(Script* script) { if ((scriptState.requests & SCRIPT_REQUEST_COMBAT) != 0 && scriptState.combatState1.attacker == script->owner) { scriptState.requests &= ~(SCRIPT_REQUEST_LOCKED | SCRIPT_REQUEST_COMBAT); } return 0; } // 0x4A3FB4 int scripts_check_state() { if (scriptState.requests == 0) { return 0; } if ((scriptState.requests & SCRIPT_REQUEST_COMBAT) != 0) { if (!action_explode_running()) { // entering combat scriptState.requests &= ~(SCRIPT_REQUEST_LOCKED | SCRIPT_REQUEST_COMBAT); memcpy(&scriptState.combatState2, &scriptState.combatState1, sizeof(scriptState.combatState2)); if ((scriptState.requests & SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE; combat(NULL); } else { combat(&scriptState.combatState2); memset(&scriptState.combatState2, 0, sizeof(scriptState.combatState2)); } } } if ((scriptState.requests & SCRIPT_REQUEST_TOWN_MAP) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_TOWN_MAP; wmTownMap(); } if ((scriptState.requests & SCRIPT_REQUEST_WORLD_MAP) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_WORLD_MAP; wmWorldMap(); } if ((scriptState.requests & SCRIPT_REQUEST_ELEVATOR) != 0) { int map = map_data.field_34; int elevation = scriptState.elevatorLevel; int tile = -1; scriptState.requests &= ~SCRIPT_REQUEST_ELEVATOR; if (elevator_select(scriptState.elevatorType, &map, &elevation, &tile) != -1) { automap_pip_save(); if (map == map_data.field_34) { if (elevation == map_elevation) { register_clear(obj_dude); obj_set_rotation(obj_dude, ROTATION_SE, 0); obj_attempt_placement(obj_dude, tile, elevation, 0); } else { Object* elevatorDoors = obj_find_first_at(obj_dude->elevation); while (elevatorDoors != NULL) { int pid = elevatorDoors->pid; if (PID_TYPE(pid) == OBJ_TYPE_SCENERY && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) && tile_dist(elevatorDoors->tile, obj_dude->tile) <= 4) { break; } elevatorDoors = obj_find_next_at(); } register_clear(obj_dude); obj_set_rotation(obj_dude, ROTATION_SE, 0); obj_attempt_placement(obj_dude, tile, elevation, 0); if (elevatorDoors != NULL) { obj_set_frame(elevatorDoors, 0, NULL); obj_move_to_tile(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; elevatorDoors->data.scenery.door.openFlags &= ~0x01; obj_rebuild_all_light(); } else { debug_printf("\nWarning: Elevator: Couldn't find old elevator doors!"); } } } else { Object* elevatorDoors = obj_find_first_at(obj_dude->elevation); while (elevatorDoors != NULL) { int pid = elevatorDoors->pid; if (PID_TYPE(pid) == OBJ_TYPE_SCENERY && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) && tile_dist(elevatorDoors->tile, obj_dude->tile) <= 4) { break; } elevatorDoors = obj_find_next_at(); } if (elevatorDoors != NULL) { obj_set_frame(elevatorDoors, 0, NULL); obj_move_to_tile(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; elevatorDoors->data.scenery.door.openFlags &= ~0x01; obj_rebuild_all_light(); } else { debug_printf("\nWarning: Elevator: Couldn't find old elevator doors!"); } MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = map; transition.elevation = elevation; transition.tile = tile; transition.rotation = ROTATION_SE; map_leave_map(&transition); } } } if ((scriptState.requests & SCRIPT_REQUEST_EXPLOSION) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_EXPLOSION; action_explode(scriptState.explosionTile, scriptState.explosionElevation, scriptState.explosionMinDamage, scriptState.explosionMaxDamage, NULL, 1); } if ((scriptState.requests & SCRIPT_REQUEST_DIALOG) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_DIALOG; gdialogEnter(scriptState.dialogTarget, 0); } if ((scriptState.requests & SCRIPT_REQUEST_ENDGAME) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_ENDGAME; endgame_slideshow(); endgame_movie(); } if ((scriptState.requests & SCRIPT_REQUEST_LOOTING) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_LOOTING; loot_container(scriptState.lootingBy, scriptState.lootingFrom); } if ((scriptState.requests & SCRIPT_REQUEST_STEALING) != 0) { scriptState.requests &= ~SCRIPT_REQUEST_STEALING; inven_steal_container(scriptState.stealingBy, scriptState.stealingFrom); } return 0; } // 0x4A43A0 int scripts_check_state_in_combat() { if ((scriptState.requests & SCRIPT_REQUEST_ELEVATOR) != 0) { int map = map_data.field_34; int elevation = scriptState.elevatorLevel; int tile = -1; if (elevator_select(scriptState.elevatorType, &map, &elevation, &tile) != -1) { automap_pip_save(); if (map == map_data.field_34) { if (elevation == map_elevation) { register_clear(obj_dude); obj_set_rotation(obj_dude, ROTATION_SE, 0); obj_attempt_placement(obj_dude, tile, elevation, 0); } else { Object* elevatorDoors = obj_find_first_at(obj_dude->elevation); while (elevatorDoors != NULL) { int pid = elevatorDoors->pid; if (PID_TYPE(pid) == OBJ_TYPE_SCENERY && (pid == PROTO_ID_0x2000099 || pid == PROTO_ID_0x20001A5 || pid == PROTO_ID_0x20001D6) && tile_dist(elevatorDoors->tile, obj_dude->tile) <= 4) { break; } elevatorDoors = obj_find_next_at(); } register_clear(obj_dude); obj_set_rotation(obj_dude, ROTATION_SE, 0); obj_attempt_placement(obj_dude, tile, elevation, 0); if (elevatorDoors != NULL) { obj_set_frame(elevatorDoors, 0, NULL); obj_move_to_tile(elevatorDoors, elevatorDoors->tile, elevatorDoors->elevation, NULL); elevatorDoors->flags &= ~OBJECT_OPEN_DOOR; elevatorDoors->data.scenery.door.openFlags &= ~0x01; obj_rebuild_all_light(); } else { debug_printf("\nWarning: Elevator: Couldn't find old elevator doors!"); } } } else { MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = map; transition.elevation = elevation; transition.tile = tile; transition.rotation = ROTATION_SE; map_leave_map(&transition); } } } if ((scriptState.requests & SCRIPT_REQUEST_LOOTING) != 0) { loot_container(scriptState.lootingBy, scriptState.lootingFrom); } // NOTE: Uninline. scripts_clear_state(); return 0; } // 0x4A457C int scripts_request_combat(STRUCT_664980* a1) { if ((scriptState.requests & SCRIPT_REQUEST_LOCKED) != 0) { return -1; } if (a1) { static_assert(sizeof(scriptState.combatState1) == sizeof(*a1), "wrong size"); memcpy(&scriptState.combatState1, a1, sizeof(scriptState.combatState1)); } else { scriptState.requests |= SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE; } scriptState.requests |= SCRIPT_REQUEST_COMBAT; return 0; } // 0x4A45D4 void scripts_request_combat_locked(STRUCT_664980* a1) { if (a1 != NULL) { memcpy(&scriptState.combatState1, a1, sizeof(scriptState.combatState1)); } else { scriptState.requests |= SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE; } scriptState.requests |= (SCRIPT_REQUEST_LOCKED | SCRIPT_REQUEST_COMBAT); } // 0x4A4644 void scripts_request_worldmap() { if (isInCombat()) { game_user_wants_to_quit = 1; } scriptState.requests |= SCRIPT_REQUEST_WORLD_MAP; } // scripts_request_elevator // 0x4A466C int scripts_request_elevator(Object* a1, int a2) { int elevatorType = a2; int elevatorLevel = map_elevation; int tile = a1->tile; if (tile == -1) { debug_printf("\nError: scripts_request_elevator! Bad tile num"); return -1; } // In the following code we are looking for an elevator. 5 tiles in each direction tile = tile - (HEX_GRID_WIDTH * 5) - 5; // left upper corner Object* obj; for (int y = -5; y < 5; y++) { for (int x = -5; x < 5; x++) { obj = obj_find_first_at(a1->elevation); while (obj != NULL) { if (tile == obj->tile && obj->pid == PROTO_ID_0x200050D) { break; } obj = obj_find_next_at(); } if (obj != NULL) { break; } tile += 1; } if (obj != NULL) { break; } tile += HEX_GRID_WIDTH - 10; } if (obj != NULL) { elevatorType = obj->data.scenery.elevator.type; elevatorLevel = obj->data.scenery.elevator.level; } if (elevatorType == -1) { return -1; } scriptState.requests |= SCRIPT_REQUEST_ELEVATOR; scriptState.elevatorType = elevatorType; scriptState.elevatorLevel = elevatorLevel; return 0; } // 0x4A4730 int scripts_request_explosion(int tile, int elevation, int minDamage, int maxDamage) { scriptState.requests |= SCRIPT_REQUEST_EXPLOSION; scriptState.explosionTile = tile; scriptState.explosionElevation = elevation; scriptState.explosionMinDamage = minDamage; scriptState.explosionMaxDamage = maxDamage; return 0; } // 0x4A4754 void scripts_request_dialog(Object* obj) { scriptState.dialogTarget = obj; scriptState.requests |= SCRIPT_REQUEST_DIALOG; } // 0x4A4770 void scripts_request_endgame_slideshow() { scriptState.requests |= SCRIPT_REQUEST_ENDGAME; } // 0x4A477C int scripts_request_loot_container(Object* a1, Object* a2) { scriptState.lootingBy = a1; scriptState.lootingFrom = a2; scriptState.requests |= SCRIPT_REQUEST_LOOTING; return 0; } // 0x4A479C int scripts_request_steal_container(Object* a1, Object* a2) { scriptState.stealingBy = a1; scriptState.stealingFrom = a2; scriptState.requests |= SCRIPT_REQUEST_STEALING; return 0; } // NOTE: Inlined. void script_make_path(char* path) { strcpy(path, cd_path_base); strcat(path, script_path_base); } // exec_script_proc // 0x4A4810 int exec_script_proc(int sid, int proc) { if (!script_engine_running) { return -1; } Script* script; if (scr_ptr(sid, &script) == -1) { return -1; } script->scriptOverrides = 0; bool programLoaded = false; if ((script->flags & SCRIPT_FLAG_0x01) == 0) { clock(); char name[16]; if (scr_index_to_name(script->field_14 & 0xFFFFFF, name) == -1) { return -1; } char* pch = strchr(name, '.'); if (pch != NULL) { *pch = '\0'; } script->program = loadProgram(name); if (script->program == NULL) { debug_printf("\nError: exec_script_proc: script load failed!"); return -1; } programLoaded = true; script->flags |= SCRIPT_FLAG_0x01; } Program* program = script->program; if (program == NULL) { return -1; } if ((program->flags & 0x0124) != 0) { return 0; } int v9 = script->procs[proc]; if (v9 == 0) { v9 = 1; } if (v9 == -1) { return -1; } if (script->target == NULL) { script->target = script->owner; } script->flags |= SCRIPT_FLAG_0x04; if (programLoaded) { scr_build_lookup_table(script); v9 = script->procs[proc]; if (v9 == 0) { v9 = 1; } script->action = 0; // NOTE: Uninline. runProgram(program); interpret(program, -1); } script->action = proc; executeProcedure(program, v9); script->source = NULL; return 0; } // Locate built-in procs for given script. // // 0x4A49D0 static int scr_build_lookup_table(Script* script) { for (int proc = 0; proc < SCRIPT_PROC_COUNT; proc++) { int index = interpretFindProcedure(script->program, procTableStrs[proc]); if (index == -1) { index = SCRIPT_PROC_NO_PROC; } script->procs[proc] = index; } return 0; } // 0x4A4A08 bool scriptHasProc(int sid, int proc) { Script* scr; if (scr_ptr(sid, &scr) == -1) { return 0; } return scr->procs[proc] != SCRIPT_PROC_NO_PROC; } // 0x4A4D50 static int scrInitListInfo() { char path[MAX_PATH]; script_make_path(path); strcat(path, "scripts.lst"); File* stream = db_fopen(path, "rt"); if (stream == NULL) { return -1; } char string[260]; while (db_fgets(string, 260, stream)) { maxScriptNum++; ScriptsListEntry* entries = (ScriptsListEntry*)mem_realloc(scriptListInfo, sizeof(*entries) * maxScriptNum); if (entries == NULL) { return -1; } scriptListInfo = entries; ScriptsListEntry* entry = &(entries[maxScriptNum - 1]); entry->local_vars_num = 0; char* substr = strstr(string, ".int"); if (substr != NULL) { int length = substr - string; if (length > 13) { return -1; } strncpy(entry->name, string, 13); entry->name[length] = '\0'; } if (strstr(string, "#") != NULL) { substr = strstr(string, "local_vars="); if (substr != NULL) { entry->local_vars_num = atoi(substr + 11); } } } db_fclose(stream); return 0; } // NOTE: Inlined. // // 0x4A4EFC static int scrExitListInfo() { if (scriptListInfo != NULL) { mem_free(scriptListInfo); scriptListInfo = NULL; } maxScriptNum = 0; return 0; } // 0x4A4F28 int scr_find_str_run_info(int scriptIndex, int* a2, int sid) { Script* script; if (scr_ptr(sid, &script) == -1) { return -1; } script->localVarsCount = scriptListInfo[scriptIndex].local_vars_num; return 0; } // 0x4A4F68 static int scr_index_to_name(int scriptIndex, char* name) { sprintf(name, "%s.int", scriptListInfo[scriptIndex].name); return 0; } // scr_set_dude_script // 0x4A4F90 int scr_set_dude_script() { if (scr_clear_dude_script() == -1) { return -1; } if (obj_dude == NULL) { debug_printf("Error in scr_set_dude_script: obj_dude uninitialized!"); return -1; } Proto* proto; if (proto_ptr(0x1000000, &proto) == -1) { debug_printf("Error in scr_set_dude_script: can't find obj_dude proto!"); return -1; } proto->critter.sid = 0x4000000; obj_new_sid(obj_dude, &(obj_dude->sid)); Script* script; if (scr_ptr(obj_dude->sid, &script) == -1) { debug_printf("Error in scr_set_dude_script: can't find obj_dude script!"); return -1; } script->flags |= (SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); return 0; } // scr_clear_dude_script // 0x4A5044 int scr_clear_dude_script() { if (obj_dude == NULL) { debug_printf("\nError in scr_clear_dude_script: obj_dude uninitialized!"); return -1; } if (obj_dude->sid != -1) { Script* script; if (scr_ptr(obj_dude->sid, &script) != -1) { script->flags &= ~(SCRIPT_FLAG_0x08 | SCRIPT_FLAG_0x10); } scr_remove(obj_dude->sid); obj_dude->sid = -1; } return 0; } // scr_init // 0x4A50A8 int scr_init() { if (!message_init(&script_message_file)) { return -1; } for (int index = 0; index < 1450; index++) { if (!message_init(&(script_dialog_msgs[index]))) { return -1; } } scr_remove_all(); interpretOutputFunc(win_debug); initInterpreter(); scr_header_load(); // NOTE: Uninline. scripts_clear_state(); partyMemberClear(); if (scrInitListInfo() == -1) { return -1; } return 0; } // 0x4A5120 int scr_reset() { scr_remove_all(); // NOTE: Uninline. scripts_clear_state(); partyMemberClear(); return 0; } // 0x4A5138 int scr_game_init() { int i; char path[MAX_PATH]; if (!message_init(&script_message_file)) { debug_printf("\nError initing script message file!"); return -1; } for (i = 0; i < 1450; i++) { if (!message_init(&(script_dialog_msgs[i]))) { debug_printf("\nERROR IN SCRIPT_DIALOG_MSGS!"); return -1; } } sprintf(path, "%s%s", msg_path, "script.msg"); if (!message_load(&script_message_file, path)) { debug_printf("\nError loading script message file!"); return -1; } script_engine_running = true; script_engine_game_mode = 1; fallout_game_time = 1; gameTimeSetTime(302400); add_bk_process(doBkProcesses); if (scr_set_dude_script() == -1) { return -1; } scrSpatialsEnabled = true; // NOTE: Uninline. scripts_clear_state(); return 0; } // 0x4A5240 int scr_game_reset() { debug_printf("\nScripts: [Game Reset]"); scr_game_exit(); scr_game_init(); partyMemberClear(); scr_remove_all_force(); return scr_set_dude_script(); } // 0x4A5274 int scr_exit() { script_engine_running = false; script_engine_run_critters = 0; if (!message_exit(&script_message_file)) { debug_printf("\nError exiting script message file!"); return -1; } scr_remove_all(); scr_remove_all_force(); interpretClose(); clearPrograms(); // NOTE: Uninline. scripts_clear_state(); // NOTE: Uninline. scrExitListInfo(); return 0; } // scr_message_free // 0x4A52F4 int scr_message_free() { for (int index = 0; index < 1450; index++) { MessageList* messageList = &(script_dialog_msgs[index]); if (messageList->entries_num != 0) { if (!message_exit(messageList)) { debug_printf("\nERROR in scr_message_free!"); return -1; } if (!message_init(messageList)) { debug_printf("\nERROR in scr_message_free!"); return -1; } } } return 0; } // 0x4A535C int scr_game_exit() { script_engine_game_mode = 0; script_engine_running = false; script_engine_run_critters = 0; scr_message_free(); scr_remove_all(); clearPrograms(); remove_bk_process(doBkProcesses); message_exit(&script_message_file); if (scr_clear_dude_script() == -1) { return -1; } // NOTE: Uninline. scripts_clear_state(); return 0; } // scr_enable // 0x4A53A8 int scr_enable() { if (!script_engine_game_mode) { return -1; } script_engine_run_critters = 1; script_engine_running = true; return 0; } // scr_disable // 0x4A53D0 int scr_disable() { script_engine_running = false; return 0; } // 0x4A53E0 void scr_enable_critters() { script_engine_run_critters = 1; } // 0x4A53F0 void scr_disable_critters() { script_engine_run_critters = 0; } // 0x4A5400 int scr_game_save(File* stream) { return db_fwriteIntCount(stream, game_global_vars, num_game_global_vars); } // 0x4A5424 int scr_game_load(File* stream) { return db_freadIntCount(stream, game_global_vars, num_game_global_vars); } // NOTE: For unknown reason save game files contains two identical sets of game // global variables (saved with [scr_game_save]). The first set is // read with [scr_game_load], the second set is simply thrown away // using this function. // // 0x4A5448 int scr_game_load2(File* stream) { int* vars = (int*)mem_malloc(sizeof(*vars) * num_game_global_vars); if (vars == NULL) { return -1; } if (db_freadIntCount(stream, vars, num_game_global_vars) == -1) { // FIXME: Leaks vars. return -1; } mem_free(vars); return 0; } // 0x4A5490 static int scr_header_load() { num_script_indexes = 0; char path[MAX_PATH]; script_make_path(path); strcat(path, "scripts.lst"); File* stream = db_fopen(path, "rt"); if (stream == NULL) { return -1; } while (1) { int ch = db_fgetc(stream); if (ch == -1) { break; } if (ch == '\n') { num_script_indexes++; } } num_script_indexes++; db_fclose(stream); for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(scriptlists[scriptType]); scriptList->head = NULL; scriptList->tail = NULL; scriptList->length = 0; scriptList->nextScriptId = 0; } return 0; } // 0x4A5590 static int scr_write_ScriptSubNode(Script* scr, File* stream) { if (db_fwriteInt(stream, scr->sid) == -1) return -1; if (db_fwriteInt(stream, scr->field_4) == -1) return -1; switch (SID_TYPE(scr->sid)) { case SCRIPT_TYPE_SPATIAL: if (db_fwriteInt(stream, scr->sp.built_tile) == -1) return -1; if (db_fwriteInt(stream, scr->sp.radius) == -1) return -1; break; case SCRIPT_TYPE_TIMED: if (db_fwriteInt(stream, scr->tm.time) == -1) return -1; break; } if (db_fwriteInt(stream, scr->flags) == -1) return -1; if (db_fwriteInt(stream, scr->field_14) == -1) return -1; if (db_fwriteInt(stream, (int)scr->program) == -1) return -1; // FIXME: writing pointer to file if (db_fwriteInt(stream, scr->field_1C) == -1) return -1; if (db_fwriteInt(stream, scr->localVarsOffset) == -1) return -1; if (db_fwriteInt(stream, scr->localVarsCount) == -1) return -1; if (db_fwriteInt(stream, scr->field_28) == -1) return -1; if (db_fwriteInt(stream, scr->action) == -1) return -1; if (db_fwriteInt(stream, scr->fixedParam) == -1) return -1; if (db_fwriteInt(stream, scr->actionBeingUsed) == -1) return -1; if (db_fwriteInt(stream, scr->scriptOverrides) == -1) return -1; if (db_fwriteInt(stream, scr->field_48) == -1) return -1; if (db_fwriteInt(stream, scr->howMuch) == -1) return -1; if (db_fwriteInt(stream, scr->field_50) == -1) return -1; return 0; } // 0x4A5704 static int scr_write_ScriptNode(ScriptListExtent* a1, File* stream) { for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) { Script* script = &(a1->scripts[index]); if (scr_write_ScriptSubNode(script, stream) != 0) { return -1; } } if (db_fwriteInt(stream, a1->length) != 0) { return -1; } if (db_fwriteInt(stream, (int)a1->next) != 0) { // FIXME: writing pointer to file return -1; } return 0; } // 0x4A5768 int scr_save(File* stream) { for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(scriptlists[scriptType]); int scriptCount = scriptList->length * SCRIPT_LIST_EXTENT_SIZE; if (scriptList->tail != NULL) { scriptCount += scriptList->tail->length - SCRIPT_LIST_EXTENT_SIZE; } ScriptListExtent* scriptExtent = scriptList->head; ScriptListExtent* lastScriptExtent = NULL; while (scriptExtent != NULL) { for (int index = 0; index < scriptExtent->length; index++) { Script* script = &(scriptExtent->scripts[index]); lastScriptExtent = scriptList->tail; if ((script->flags & SCRIPT_FLAG_0x08) != 0) { scriptCount--; int backwardsIndex = lastScriptExtent->length - 1; if (lastScriptExtent == scriptExtent && backwardsIndex <= index) { break; } while (lastScriptExtent != scriptExtent || backwardsIndex > index) { Script* backwardsScript = &(lastScriptExtent->scripts[backwardsIndex]); if ((backwardsScript->flags & SCRIPT_FLAG_0x08) == 0) { break; } backwardsIndex--; if (backwardsIndex < 0) { ScriptListExtent* previousScriptExtent = scriptList->head; while (previousScriptExtent->next != lastScriptExtent) { previousScriptExtent = previousScriptExtent->next; } lastScriptExtent = previousScriptExtent; backwardsIndex = lastScriptExtent->length - 1; } } if (lastScriptExtent != scriptExtent || backwardsIndex > index) { Script temp; memcpy(&temp, script, sizeof(Script)); memcpy(script, &(lastScriptExtent->scripts[backwardsIndex]), sizeof(Script)); memcpy(&(lastScriptExtent->scripts[backwardsIndex]), &temp, sizeof(Script)); scriptCount++; } } } scriptExtent = scriptExtent->next; } if (db_fwriteInt(stream, scriptCount) == -1) { return -1; } if (scriptCount > 0) { ScriptListExtent* scriptExtent = scriptList->head; while (scriptExtent != lastScriptExtent) { if (scr_write_ScriptNode(scriptExtent, stream) == -1) { return -1; } scriptExtent = scriptExtent->next; } if (lastScriptExtent != NULL) { int index; for (index = 0; index < lastScriptExtent->length; index++) { Script* script = &(lastScriptExtent->scripts[index]); if ((script->flags & SCRIPT_FLAG_0x08) != 0) { break; } } if (index > 0) { int length = lastScriptExtent->length; lastScriptExtent->length = index; if (scr_write_ScriptNode(lastScriptExtent, stream) == -1) { return -1; } lastScriptExtent->length = length; } } } } return 0; } // 0x4A5A1C static int scr_read_ScriptSubNode(Script* scr, File* stream) { int prg; if (db_freadInt(stream, &(scr->sid)) == -1) return -1; if (db_freadInt(stream, &(scr->field_4)) == -1) return -1; switch (SID_TYPE(scr->sid)) { case SCRIPT_TYPE_SPATIAL: if (db_freadInt(stream, &(scr->sp.built_tile)) == -1) return -1; if (db_freadInt(stream, &(scr->sp.radius)) == -1) return -1; break; case SCRIPT_TYPE_TIMED: if (db_freadInt(stream, &(scr->tm.time)) == -1) return -1; break; } if (db_freadInt(stream, &(scr->flags)) == -1) return -1; if (db_freadInt(stream, &(scr->field_14)) == -1) return -1; if (db_freadInt(stream, &(prg)) == -1) return -1; if (db_freadInt(stream, &(scr->field_1C)) == -1) return -1; if (db_freadInt(stream, &(scr->localVarsOffset)) == -1) return -1; if (db_freadInt(stream, &(scr->localVarsCount)) == -1) return -1; if (db_freadInt(stream, &(scr->field_28)) == -1) return -1; if (db_freadInt(stream, &(scr->action)) == -1) return -1; if (db_freadInt(stream, &(scr->fixedParam)) == -1) return -1; if (db_freadInt(stream, &(scr->actionBeingUsed)) == -1) return -1; if (db_freadInt(stream, &(scr->scriptOverrides)) == -1) return -1; if (db_freadInt(stream, &(scr->field_48)) == -1) return -1; if (db_freadInt(stream, &(scr->howMuch)) == -1) return -1; if (db_freadInt(stream, &(scr->field_50)) == -1) return -1; scr->program = NULL; scr->owner = NULL; scr->source = NULL; scr->target = NULL; for (int index = 0; index < SCRIPT_PROC_COUNT; index++) { scr->procs[index] = 0; } if (!(map_data.flags & 1)) { scr->localVarsCount = 0; } return 0; } // 0x4A5BE8 static int scr_read_ScriptNode(ScriptListExtent* scriptExtent, File* stream) { for (int index = 0; index < SCRIPT_LIST_EXTENT_SIZE; index++) { Script* scr = &(scriptExtent->scripts[index]); if (scr_read_ScriptSubNode(scr, stream) != 0) { return -1; } } if (db_freadInt(stream, &(scriptExtent->length)) != 0) { return -1; } int next; if (db_freadInt(stream, &(next)) != 0) { return -1; } return 0; } // 0x4A5C50 int scr_load(File* stream) { for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) { ScriptList* scriptList = &(scriptlists[index]); int scriptsCount = 0; if (db_freadInt(stream, &scriptsCount) == -1) { return -1; } if (scriptsCount != 0) { scriptList->length = scriptsCount / 16; if (scriptsCount % 16 != 0) { scriptList->length++; } ScriptListExtent* extent = (ScriptListExtent*)mem_malloc(sizeof(*extent)); scriptList->head = extent; scriptList->tail = extent; if (extent == NULL) { return -1; } if (scr_read_ScriptNode(extent, stream) != 0) { return -1; } for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { Script* script = &(extent->scripts[scriptIndex]); script->owner = NULL; script->source = NULL; script->target = NULL; script->program = NULL; script->flags &= ~SCRIPT_FLAG_0x01; } extent->next = NULL; ScriptListExtent* prevExtent = extent; for (int extentIndex = 1; extentIndex < scriptList->length; extentIndex++) { ScriptListExtent* extent = (ScriptListExtent*)mem_malloc(sizeof(*extent)); if (extent == NULL) { return -1; } if (scr_read_ScriptNode(extent, stream) != 0) { return -1; } for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { Script* script = &(extent->scripts[scriptIndex]); script->owner = NULL; script->source = NULL; script->target = NULL; script->program = NULL; script->flags &= ~SCRIPT_FLAG_0x01; } prevExtent->next = extent; extent->next = NULL; prevExtent = extent; } scriptList->tail = prevExtent; } else { scriptList->head = NULL; scriptList->tail = NULL; scriptList->length = 0; } } return 0; } // scr_ptr // 0x4A5E34 int scr_ptr(int sid, Script** scriptPtr) { *scriptPtr = NULL; if (sid == -1) { return -1; } if (sid == 0xCCCCCCCC) { debug_printf("\nERROR: scr_ptr called with UN-SET id #!!!!"); return -1; } ScriptList* scriptList = &(scriptlists[SID_TYPE(sid)]); ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { for (int index = 0; index < scriptListExtent->length; index++) { Script* script = &(scriptListExtent->scripts[index]); if (script->sid == sid) { *scriptPtr = script; return 0; } } scriptListExtent = scriptListExtent->next; } return -1; } // 0x4A5ED8 static int scr_new_id(int scriptType) { int scriptId = scriptlists[scriptType].nextScriptId++; int v1 = scriptType << 24; while (scriptId < 32000) { Script* script; if (scr_ptr(v1 | scriptId, &script) == -1) { break; } scriptId++; } return scriptId; } // 0x4A5F28 int scr_new(int* sidPtr, int scriptType) { ScriptList* scriptList = &(scriptlists[scriptType]); ScriptListExtent* scriptListExtent = scriptList->tail; if (scriptList->head != NULL) { // There is at least one extent available, which means tail is also set. if (scriptListExtent->length == SCRIPT_LIST_EXTENT_SIZE) { ScriptListExtent* newExtent = scriptListExtent->next = (ScriptListExtent*)mem_malloc(sizeof(*newExtent)); if (newExtent == NULL) { return -1; } newExtent->length = 0; newExtent->next = NULL; scriptList->tail = newExtent; scriptList->length++; scriptListExtent = newExtent; } } else { // Script head scriptListExtent = (ScriptListExtent*)mem_malloc(sizeof(ScriptListExtent)); if (scriptListExtent == NULL) { return -1; } scriptListExtent->length = 0; scriptListExtent->next = NULL; scriptList->head = scriptListExtent; scriptList->tail = scriptListExtent; scriptList->length = 1; } int sid = scr_new_id(scriptType) | (scriptType << 24); *sidPtr = sid; Script* scr = &(scriptListExtent->scripts[scriptListExtent->length]); scr->sid = sid; scr->sp.built_tile = -1; scr->sp.radius = -1; scr->flags = 0; scr->field_14 = -1; scr->program = 0; scr->localVarsOffset = -1; scr->localVarsCount = 0; scr->field_28 = 0; scr->action = 0; scr->fixedParam = 0; scr->owner = 0; scr->source = 0; scr->target = 0; scr->actionBeingUsed = -1; scr->scriptOverrides = 0; scr->field_48 = 0; scr->howMuch = 0; scr->field_50 = 0; for (int index = 0; index < SCRIPT_PROC_COUNT; index++) { scr->procs[index] = SCRIPT_PROC_NO_PROC; } scriptListExtent->length++; return 0; } // scr_remove_local_vars // 0x4A60D4 int scr_remove_local_vars(Script* script) { if (script == NULL) { return -1; } if (script->localVarsCount != 0) { int oldMapLocalVarsCount = num_map_local_vars; if (oldMapLocalVarsCount > 0 && script->localVarsOffset >= 0) { num_map_local_vars -= script->localVarsCount; if (oldMapLocalVarsCount - script->localVarsCount != script->localVarsOffset && script->localVarsOffset != -1) { memmove(map_local_vars + script->localVarsOffset, map_local_vars + (script->localVarsOffset + script->localVarsCount), sizeof(*map_local_vars) * (oldMapLocalVarsCount - script->localVarsCount - script->localVarsOffset)); map_local_vars = (int*)mem_realloc(map_local_vars, sizeof(*map_local_vars) * num_map_local_vars); if (map_local_vars == NULL) { debug_printf("\nError in mem_realloc in scr_remove_local_vars!\n"); } for (int index = 0; index < SCRIPT_TYPE_COUNT; index++) { ScriptList* scriptList = &(scriptlists[index]); ScriptListExtent* extent = scriptList->head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* other = &(extent->scripts[index]); if (other->localVarsOffset > script->localVarsOffset) { other->localVarsOffset -= script->localVarsCount; } } extent = extent->next; } } } } } return 0; } // scr_remove // 0x4A61D4 int scr_remove(int sid) { if (sid == -1) { return -1; } ScriptList* scriptList = &(scriptlists[SID_TYPE(sid)]); ScriptListExtent* scriptListExtent = scriptList->head; int index; while (scriptListExtent != NULL) { for (index = 0; index < scriptListExtent->length; index++) { Script* script = &(scriptListExtent->scripts[index]); if (script->sid == sid) { break; } } if (index < scriptListExtent->length) { break; } scriptListExtent = scriptListExtent->next; } if (scriptListExtent == NULL) { return -1; } Script* script = &(scriptListExtent->scripts[index]); if ((script->flags & SCRIPT_FLAG_0x02) != 0) { if (script->program != NULL) { script->program = NULL; } } if ((script->flags & SCRIPT_FLAG_0x10) == 0) { // NOTE: Uninline. scripts_clear_combat_requests(script); if (scr_remove_local_vars(script) == -1) { debug_printf("\nERROR Removing local vars on scr_remove!!\n"); } if (queue_remove_this(script->owner, EVENT_TYPE_SCRIPT) == -1) { debug_printf("\nERROR Removing Timed Events on scr_remove!!\n"); } if (scriptListExtent == scriptList->tail && index + 1 == scriptListExtent->length) { // Removing last script in tail extent scriptListExtent->length -= 1; if (scriptListExtent->length == 0) { scriptList->length--; mem_free(scriptListExtent); if (scriptList->length != 0) { ScriptListExtent* v13 = scriptList->head; while (scriptList->tail != v13->next) { v13 = v13->next; } v13->next = NULL; scriptList->tail = v13; } else { scriptList->head = NULL; scriptList->tail = NULL; } } } else { // Relocate last script from tail extent into this script's slot. memcpy(&(scriptListExtent->scripts[index]), &(scriptList->tail->scripts[scriptList->tail->length - 1]), sizeof(Script)); // Decrement number of scripts in tail extent. scriptList->tail->length -= 1; // Check to see if this extent became empty. if (scriptList->tail->length == 0) { scriptList->length -= 1; // Find previous extent that is about to become a new tail for // this script list. ScriptListExtent* prev = scriptList->head; while (prev->next != scriptList->tail) { prev = prev->next; } prev->next = NULL; mem_free(scriptList->tail); scriptList->tail = prev; } } } return 0; } // 0x4A63E0 int scr_remove_all() { queue_clear_type(EVENT_TYPE_SCRIPT, NULL); scr_message_free(); for (int scrType = 0; scrType < SCRIPT_TYPE_COUNT; scrType++) { ScriptList* scriptList = &(scriptlists[scrType]); // TODO: Super odd way to remove scripts. The problem is that [scrRemove] // does relocate scripts between extents, so current extent may become // empty. In addition there is a 0x10 flag on the script that is not // removed. Find a way to refactor this. ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { ScriptListExtent* next = NULL; for (int scriptIndex = 0; scriptIndex < scriptListExtent->length;) { Script* script = &(scriptListExtent->scripts[scriptIndex]); if ((script->flags & SCRIPT_FLAG_0x10) != 0) { scriptIndex++; } else { if (scriptIndex != 0 || scriptListExtent->length != 1) { scr_remove(script->sid); } else { next = scriptListExtent->next; scr_remove(script->sid); } } } scriptListExtent = next; } } scr_find_first_idx = 0; scr_find_first_ptr = NULL; scr_find_first_elev = 0; map_script_id = -1; clearPrograms(); exportClearAllVariables(); return 0; } // 0x4A64A8 int scr_remove_all_force() { queue_clear_type(EVENT_TYPE_SCRIPT, NULL); scr_message_free(); for (int type = 0; type < SCRIPT_TYPE_COUNT; type++) { ScriptList* scriptList = &(scriptlists[type]); ScriptListExtent* extent = scriptList->head; while (extent != NULL) { ScriptListExtent* next = extent->next; mem_free(extent); extent = next; } scriptList->head = NULL; scriptList->tail = NULL; scriptList->length = 0; } scr_find_first_idx = 0; scr_find_first_ptr = 0; scr_find_first_elev = 0; map_script_id = -1; clearPrograms(); exportClearAllVariables(); return 0; } // 0x4A6524 Script* scr_find_first_at(int elevation) { scr_find_first_elev = elevation; scr_find_first_idx = 0; scr_find_first_ptr = scriptlists[SCRIPT_TYPE_SPATIAL].head; if (scr_find_first_ptr == NULL) { return NULL; } Script* script = &(scr_find_first_ptr->scripts[0]); if ((script->flags & SCRIPT_FLAG_0x02) != 0 || builtTileGetElevation(script->sp.built_tile) != elevation) { script = scr_find_next_at(); } return script; } // 0x4A6564 Script* scr_find_next_at() { ScriptListExtent* scriptListExtent = scr_find_first_ptr; int scriptIndex = scr_find_first_idx; if (scriptListExtent == NULL) { return NULL; } for (;;) { scriptIndex++; if (scriptIndex == SCRIPT_LIST_EXTENT_SIZE) { scriptListExtent = scriptListExtent->next; scriptIndex = 0; } else if (scriptIndex >= scriptListExtent->length) { scriptListExtent = NULL; } if (scriptListExtent == NULL) { break; } Script* script = &(scriptListExtent->scripts[scriptIndex]); if ((script->flags & SCRIPT_FLAG_0x02) == 0 && builtTileGetElevation(script->sp.built_tile) == scr_find_first_elev) { break; } } Script* script; if (scriptListExtent != NULL) { script = &(scriptListExtent->scripts[scriptIndex]); } else { script = NULL; } scr_find_first_idx = scriptIndex; scr_find_first_ptr = scriptListExtent; return script; } // 0x4A65F0 void scr_spatials_enable() { scrSpatialsEnabled = true; } // 0x4A6600 void scr_spatials_disable() { scrSpatialsEnabled = false; } // 0x4A6610 bool scr_chk_spatials_in(Object* object, int tile, int elevation) { if (object == obj_mouse) { return false; } if (object == obj_mouse_flat) { return false; } if ((object->flags & OBJECT_HIDDEN) != 0 || (object->flags & OBJECT_FLAT) != 0) { return false; } if (tile < 10) { return false; } if (!scrSpatialsEnabled) { return false; } scrSpatialsEnabled = false; int builtTile = builtTileCreate(tile, elevation); for (Script* script = scr_find_first_at(elevation); script != NULL; script = scr_find_next_at()) { if (builtTile == script->sp.built_tile) { // NOTE: Uninline. scr_set_objs(script->sid, object, NULL); } else { if (script->sp.radius == 0) { continue; } int distance = tile_dist(builtTileGetTile(script->sp.built_tile), tile); if (distance > script->sp.radius) { continue; } // NOTE: Uninline. scr_set_objs(script->sid, object, NULL); } exec_script_proc(script->sid, SCRIPT_PROC_SPATIAL); } scrSpatialsEnabled = true; return true; } // 0x4A677C int scr_load_all_scripts() { for (int scriptListIndex = 0; scriptListIndex < SCRIPT_TYPE_COUNT; scriptListIndex++) { ScriptList* scriptList = &(scriptlists[scriptListIndex]); ScriptListExtent* extent = scriptList->head; while (extent != NULL) { for (int scriptIndex = 0; scriptIndex < extent->length; scriptIndex++) { Script* script = &(extent->scripts[scriptIndex]); exec_script_proc(script->sid, SCRIPT_PROC_START); } extent = extent->next; } } return 0; } // 0x4A67DC void scr_exec_map_enter_scripts() { scrExecMapProcScripts(SCRIPT_PROC_MAP_ENTER); } // 0x4A67E4 void scr_exec_map_update_scripts() { scrExecMapProcScripts(SCRIPT_PROC_MAP_UPDATE); } // 0x4A67EC static void scrExecMapProcScripts(int proc) { scrSpatialsEnabled = false; int fixedParam = 0; if (proc == SCRIPT_PROC_MAP_ENTER) { fixedParam = (map_data.flags & 1) == 0; } else { exec_script_proc(map_script_id, proc); } int sidListCapacity = 0; for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(scriptlists[scriptType]); ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { sidListCapacity += scriptListExtent->length; scriptListExtent = scriptListExtent->next; } } if (sidListCapacity == 0) { return; } int* sidList = (int*)mem_malloc(sizeof(*sidList) * sidListCapacity); if (sidList == NULL) { debug_printf("\nError: scr_exec_map_update_scripts: Out of memory for sidList!"); return; } int sidListLength = 0; for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { ScriptList* scriptList = &(scriptlists[scriptType]); ScriptListExtent* scriptListExtent = scriptList->head; while (scriptListExtent != NULL) { for (int scriptIndex = 0; scriptIndex < scriptListExtent->length; scriptIndex++) { Script* script = &(scriptListExtent->scripts[scriptIndex]); if (script->sid != map_script_id && script->procs[proc] > 0) { sidList[sidListLength++] = script->sid; } } scriptListExtent = scriptListExtent->next; } } if (proc == SCRIPT_PROC_MAP_ENTER) { for (int index = 0; index < sidListLength; index++) { Script* script; if (scr_ptr(sidList[index], &script) != -1) { script->fixedParam = fixedParam; } exec_script_proc(sidList[index], proc); } } else { for (int index = 0; index < sidListLength; index++) { exec_script_proc(sidList[index], proc); } } mem_free(sidList); scrSpatialsEnabled = true; } // 0x4A69A0 void scr_exec_map_exit_scripts() { scrExecMapProcScripts(SCRIPT_PROC_MAP_EXIT); } // 0x4A6B64 int scr_get_dialog_msg_file(int a1, MessageList** messageListPtr) { if (a1 == -1) { return -1; } int messageListIndex = a1 - 1; MessageList* messageList = &(script_dialog_msgs[messageListIndex]); if (messageList->entries_num == 0) { char scriptName[20]; scriptName[0] = '\0'; scr_index_to_name(messageListIndex & 0xFFFFFF, scriptName); char* pch = strrchr(scriptName, '.'); if (pch != NULL) { *pch = '\0'; } char path[MAX_PATH]; sprintf(path, "dialog\\%s.msg", scriptName); if (!message_load(messageList, path)) { debug_printf("\nError loading script dialog message file!"); return -1; } if (!message_filter(messageList)) { debug_printf("\nError filtering script dialog message file!"); return -1; } } *messageListPtr = messageList; return 0; } // 0x4A6C50 char* scr_get_msg_str(int messageListId, int messageId) { return scr_get_msg_str_speech(messageListId, messageId, 0); } // 0x4A6C5C char* scr_get_msg_str_speech(int messageListId, int messageId, int a3) { // 0x51C7F0 static char err_str[] = "Error"; // 0x51C7F4 static char blank_str[] = ""; if (messageListId == 0 && messageId == 0) { return blank_str; } if (messageListId == -1 && messageId == -1) { return blank_str; } if (messageListId == -2 && messageId == -2) { MessageListItem messageListItem; return getmsg(&proto_main_msg_file, &messageListItem, 650); } MessageList* messageList; if (scr_get_dialog_msg_file(messageListId, &messageList) == -1) { debug_printf("\nERROR: message_str: can't find message file: List: %d!", messageListId); return NULL; } if (FID_TYPE(dialogue_head) != OBJ_TYPE_HEAD) { a3 = 0; } MessageListItem messageListItem; messageListItem.num = messageId; if (!message_search(messageList, &messageListItem)) { debug_printf("\nError: can't find message: List: %d, Num: %d!", messageListId, messageId); return err_str; } if (a3) { if (gdialogActive()) { if (messageListItem.audio != NULL && messageListItem.audio[0] != '\0') { if (messageListItem.flags & 0x01) { gdialogSetupSpeech(NULL); } else { gdialogSetupSpeech(messageListItem.audio); } } else { debug_printf("Missing speech name: %d\n", messageListItem.num); } } } return messageListItem.text; } // 0x4A6D64 int scr_get_local_var(int sid, int variable, int* value) { // 0x667750 static char tempStr[20]; if (SID_TYPE(sid) == SCRIPT_TYPE_SYSTEM) { debug_printf("\nError! System scripts/Map scripts not allowed local_vars! "); tempStr[0] = '\0'; scr_index_to_name(sid & 0xFFFFFF, tempStr); debug_printf(":%s\n", tempStr); *value = -1; return -1; } Script* script; if (scr_ptr(sid, &script) == -1) { *value = -1; return -1; } if (script->localVarsCount == 0) { // NOTE: Uninline. scr_find_str_run_info(script->field_14, &(script->field_50), sid); } if (script->localVarsCount > 0) { if (script->localVarsOffset == -1) { script->localVarsOffset = map_malloc_local_var(script->localVarsCount); } *value = map_get_local_var(script->localVarsOffset + variable); } return 0; } // 0x4A6E58 int scr_set_local_var(int sid, int variable, int value) { Script* script; if (scr_ptr(sid, &script) == -1) { return -1; } if (script->localVarsCount == 0) { // NOTE: Uninline. scr_find_str_run_info(script->field_14, &(script->field_50), sid); } if (script->localVarsCount <= 0) { return -1; } if (script->localVarsOffset == -1) { script->localVarsOffset = map_malloc_local_var(script->localVarsCount); } map_set_local_var(script->localVarsOffset + variable, value); return 0; } // Performs combat script and returns true if default action has been overriden // by script. // // 0x4A6EFC bool scr_end_combat() { if (map_script_id == 0 || map_script_id == -1) { return false; } int team = combat_player_knocked_out_by(); if (team == -1) { return false; } Script* before; if (scr_ptr(map_script_id, &before) != -1) { before->fixedParam = team; } exec_script_proc(map_script_id, SCRIPT_PROC_COMBAT); bool success = false; Script* after; if (scr_ptr(map_script_id, &after) != -1) { if (after->scriptOverrides != 0) { success = true; } } return success; } // 0x4A6F70 int scr_explode_scenery(Object* a1, int tile, int radius, int elevation) { int scriptExtentsCount = scriptlists[SCRIPT_TYPE_SPATIAL].length + scriptlists[SCRIPT_TYPE_ITEM].length; if (scriptExtentsCount == 0) { return 0; } int* scriptIds = (int*)mem_malloc(sizeof(*scriptIds) * scriptExtentsCount * SCRIPT_LIST_EXTENT_SIZE); if (scriptIds == NULL) { return -1; } ScriptListExtent* extent; int scriptsCount = 0; scrSpatialsEnabled = false; extent = scriptlists[SCRIPT_TYPE_ITEM].head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* script = &(extent->scripts[index]); if (script->procs[SCRIPT_PROC_DAMAGE] <= 0 && script->program == NULL) { exec_script_proc(script->sid, SCRIPT_PROC_START); } if (script->procs[SCRIPT_PROC_DAMAGE] > 0) { Object* self = script->owner; if (self != NULL) { if (self->elevation == elevation && tile_dist(self->tile, tile) <= radius) { scriptIds[scriptsCount] = script->sid; scriptsCount += 1; } } } } extent = extent->next; } extent = scriptlists[SCRIPT_TYPE_SPATIAL].head; while (extent != NULL) { for (int index = 0; index < extent->length; index++) { Script* script = &(extent->scripts[index]); if (script->procs[SCRIPT_PROC_DAMAGE] <= 0 && script->program == NULL) { exec_script_proc(script->sid, SCRIPT_PROC_START); } if (script->procs[SCRIPT_PROC_DAMAGE] > 0 && builtTileGetElevation(script->sp.built_tile) == elevation && tile_dist(builtTileGetTile(script->sp.built_tile), tile) <= radius) { scriptIds[scriptsCount] = script->sid; scriptsCount += 1; } } extent = extent->next; } for (int index = 0; index < scriptsCount; index++) { Script* script; int sid = scriptIds[index]; if (scr_ptr(sid, &script) != -1) { script->fixedParam = 20; } // TODO: Obtaining script twice, probably some inlining. if (scr_ptr(sid, &script) != -1) { script->source = NULL; script->target = a1; } exec_script_proc(sid, SCRIPT_PROC_DAMAGE); } // TODO: Redundant, we already know `scriptIds` is not NULL. if (scriptIds != NULL) { mem_free(scriptIds); } scrSpatialsEnabled = true; return 0; } ================================================ FILE: src/game/scripts.h ================================================ #ifndef FALLOUT_GAME_SCRIPTS_H_ #define FALLOUT_GAME_SCRIPTS_H_ #include #include "game/combat_defs.h" #include "plib/db/db.h" #include "int/intrpret.h" #include "game/message.h" #include "game/object_types.h" #define SCRIPT_FLAG_0x01 0x01 #define SCRIPT_FLAG_0x02 0x02 #define SCRIPT_FLAG_0x04 0x04 #define SCRIPT_FLAG_0x08 0x08 #define SCRIPT_FLAG_0x10 0x10 // 60 * 60 * 10 #define GAME_TIME_TICKS_PER_HOUR 36000 // 24 * 60 * 60 * 10 #define GAME_TIME_TICKS_PER_DAY 864000 // 365 * 24 * 60 * 60 * 10 #define GAME_TIME_TICKS_PER_YEAR 315360000 typedef enum ScriptRequests { SCRIPT_REQUEST_COMBAT = 0x01, SCRIPT_REQUEST_TOWN_MAP = 0x02, SCRIPT_REQUEST_WORLD_MAP = 0x04, SCRIPT_REQUEST_ELEVATOR = 0x08, SCRIPT_REQUEST_EXPLOSION = 0x10, SCRIPT_REQUEST_DIALOG = 0x20, SCRIPT_REQUEST_NO_INITIAL_COMBAT_STATE = 0x40, SCRIPT_REQUEST_ENDGAME = 0x80, SCRIPT_REQUEST_LOOTING = 0x100, SCRIPT_REQUEST_STEALING = 0x200, SCRIPT_REQUEST_LOCKED = 0x400, } ScriptRequests; typedef enum ScriptType { SCRIPT_TYPE_SYSTEM, // s_system SCRIPT_TYPE_SPATIAL, // s_spatial SCRIPT_TYPE_TIMED, // s_time SCRIPT_TYPE_ITEM, // s_item SCRIPT_TYPE_CRITTER, // s_critter SCRIPT_TYPE_COUNT, } ScriptType; typedef enum ScriptProc { SCRIPT_PROC_NO_PROC = 0, SCRIPT_PROC_START = 1, SCRIPT_PROC_SPATIAL = 2, SCRIPT_PROC_DESCRIPTION = 3, SCRIPT_PROC_PICKUP = 4, SCRIPT_PROC_DROP = 5, SCRIPT_PROC_USE = 6, SCRIPT_PROC_USE_OBJ_ON = 7, SCRIPT_PROC_USE_SKILL_ON = 8, SCRIPT_PROC_9 = 9, // use_ad_on_proc SCRIPT_PROC_10 = 10, // use_disad_on_proc SCRIPT_PROC_TALK = 11, SCRIPT_PROC_CRITTER = 12, SCRIPT_PROC_COMBAT = 13, SCRIPT_PROC_DAMAGE = 14, SCRIPT_PROC_MAP_ENTER = 15, SCRIPT_PROC_MAP_EXIT = 16, SCRIPT_PROC_CREATE = 17, SCRIPT_PROC_DESTROY = 18, SCRIPT_PROC_19 = 19, // barter_init_proc SCRIPT_PROC_20 = 20, // barter_proc SCRIPT_PROC_LOOK_AT = 21, SCRIPT_PROC_TIMED = 22, SCRIPT_PROC_MAP_UPDATE = 23, SCRIPT_PROC_PUSH = 24, SCRIPT_PROC_IS_DROPPING = 25, SCRIPT_PROC_COMBAT_IS_STARTING = 26, SCRIPT_PROC_COMBAT_IS_OVER = 27, SCRIPT_PROC_COUNT, } ScriptProc; static_assert(SCRIPT_PROC_COUNT == 28, "wrong count"); typedef struct ScriptsListEntry { char name[16]; int local_vars_num; } ScriptsListEntry; typedef struct Script { // scr_id int sid; // scr_next int field_4; union { struct { // scr_udata.sp.built_tile int built_tile; // scr_udata.sp.radius int radius; } sp; struct { // scr_udata.tm.time int time; } tm; }; // scr_flags int flags; // scr_script_idx int field_14; Program* program; // scr_oid int field_1C; // scr_local_var_offset int localVarsOffset; // scr_num_local_vars int localVarsCount; // return value? int field_28; // Currently executed action. // // See [op_script_action]. int action; int fixedParam; Object* owner; // source_obj Object* source; // target_obj Object* target; int actionBeingUsed; int scriptOverrides; int field_48; int howMuch; int field_50; int procs[SCRIPT_PROC_COUNT]; int field_C4; int field_C8; int field_CC; int field_D0; int field_D4; int field_D8; int field_DC; } Script; static_assert(sizeof(Script) == 0xE0, "wrong size"); extern int num_script_indexes; extern Object* scrQueueTestObj; extern int scrQueueTestValue; extern MessageList script_dialog_msgs[1450]; extern MessageList script_message_file; int game_time(); void game_time_date(int* monthPtr, int* dayPtr, int* yearPtr); int game_time_hour(); char* game_time_hour_str(); void inc_game_time(int a1); void inc_game_time_in_seconds(int a1); void gameTimeSetTime(int time); int gtime_q_add(); int gtime_q_process(Object* obj, void* data); int scriptsCheckGameEvents(int* moviePtr, int window); int scr_map_q_process(Object* obj, void* data); int new_obj_id(); int scr_find_sid_from_program(Program* program); Object* scr_find_obj_from_program(Program* program); int scr_set_objs(int sid, Object* source, Object* target); void scr_set_ext_param(int a1, int a2); int scr_set_action_num(int sid, int a2); Program* loadProgram(const char* name); void scrSetQueueTestVals(Object* a1, int a2); int scrQueueRemoveFixed(Object* obj, void* data); int script_q_add(int sid, int delay, int param); int script_q_save(File* stream, void* data); int script_q_load(File* stream, void** dataPtr); int script_q_process(Object* obj, void* data); int scripts_clear_state(); int scripts_clear_combat_requests(Script* script); int scripts_check_state(); int scripts_check_state_in_combat(); int scripts_request_combat(STRUCT_664980* a1); void scripts_request_combat_locked(STRUCT_664980* ptr); void scripts_request_worldmap(); int scripts_request_elevator(Object* a1, int a2); int scripts_request_explosion(int tile, int elevation, int minDamage, int maxDamage); void scripts_request_dialog(Object* a1); void scripts_request_endgame_slideshow(); int scripts_request_loot_container(Object* a1, Object* a2); int scripts_request_steal_container(Object* a1, Object* a2); void script_make_path(char* path); int exec_script_proc(int sid, int proc); bool scriptHasProc(int sid, int proc); int scr_find_str_run_info(int a1, int* a2, int sid); int scr_set_dude_script(); int scr_clear_dude_script(); int scr_init(); int scr_reset(); int scr_game_init(); int scr_game_reset(); int scr_exit(); int scr_message_free(); int scr_game_exit(); int scr_enable(); int scr_disable(); void scr_enable_critters(); void scr_disable_critters(); int scr_game_save(File* stream); int scr_game_load(File* stream); int scr_game_load2(File* stream); int scr_save(File* stream); int scr_load(File* stream); int scr_ptr(int sid, Script** script); int scr_new(int* sidPtr, int scriptType); int scr_remove_local_vars(Script* script); int scr_remove(int index); int scr_remove_all(); int scr_remove_all_force(); Script* scr_find_first_at(int elevation); Script* scr_find_next_at(); void scr_spatials_enable(); void scr_spatials_disable(); bool scr_chk_spatials_in(Object* obj, int tile, int elevation); int scr_load_all_scripts(); void scr_exec_map_enter_scripts(); void scr_exec_map_update_scripts(); void scr_exec_map_exit_scripts(); int scr_get_dialog_msg_file(int a1, MessageList** out_message_list); char* scr_get_msg_str(int messageListId, int messageId); char* scr_get_msg_str_speech(int messageListId, int messageId, int a3); int scr_get_local_var(int a1, int a2, int* a3); int scr_set_local_var(int a1, int a2, int a3); bool scr_end_combat(); int scr_explode_scenery(Object* a1, int tile, int radius, int elevation); #endif /* FALLOUT_GAME_SCRIPTS_H_ */ ================================================ FILE: src/game/select.c ================================================ #include "game/select.h" #include #include #include "game/art.h" #include "game/editor.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/db/db.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gsound.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object.h" #include "game/options.h" #include "game/palette.h" #include "game/proto.h" #include "game/skill.h" #include "game/stat.h" #include "plib/gnw/text.h" #include "game/trait.h" #include "plib/gnw/gnw.h" #define CS_WINDOW_WIDTH 640 #define CS_WINDOW_HEIGHT 480 #define CS_WINDOW_BACKGROUND_X 40 #define CS_WINDOW_BACKGROUND_Y 30 #define CS_WINDOW_BACKGROUND_WIDTH 560 #define CS_WINDOW_BACKGROUND_HEIGHT 300 #define CS_WINDOW_PREVIOUS_BUTTON_X 292 #define CS_WINDOW_PREVIOUS_BUTTON_Y 320 #define CS_WINDOW_NEXT_BUTTON_X 318 #define CS_WINDOW_NEXT_BUTTON_Y 320 #define CS_WINDOW_TAKE_BUTTON_X 81 #define CS_WINDOW_TAKE_BUTTON_Y 323 #define CS_WINDOW_MODIFY_BUTTON_X 435 #define CS_WINDOW_MODIFY_BUTTON_Y 320 #define CS_WINDOW_CREATE_BUTTON_X 80 #define CS_WINDOW_CREATE_BUTTON_Y 425 #define CS_WINDOW_BACK_BUTTON_X 461 #define CS_WINDOW_BACK_BUTTON_Y 425 #define CS_WINDOW_NAME_MID_X 318 #define CS_WINDOW_PRIMARY_STAT_MID_X 362 #define CS_WINDOW_SECONDARY_STAT_MID_X 379 #define CS_WINDOW_BIO_X 438 typedef enum PremadeCharacter { PREMADE_CHARACTER_NARG, PREMADE_CHARACTER_CHITSA, PREMADE_CHARACTER_MINGUN, PREMADE_CHARACTER_COUNT, } PremadeCharacter; typedef struct PremadeCharacterDescription { char fileName[20]; int face; char field_18[20]; } PremadeCharacterDescription; static void select_exit(); static bool select_update_display(); static bool select_display_portrait(); static bool select_display_stats(); static bool select_display_bio(); static bool select_fatal_error(bool rc); // 0x51C84C static int premade_index = PREMADE_CHARACTER_NARG; // 0x51C850 static PremadeCharacterDescription premade_characters[PREMADE_CHARACTER_COUNT] = { { "premade\\combat", 201, "VID 208-197-88-125" }, { "premade\\stealth", 202, "VID 208-206-49-229" }, { "premade\\diplomat", 203, "VID 208-206-49-227" }, }; // 0x51C8D4 static int premade_total = PREMADE_CHARACTER_COUNT; // 0x51C7F8 int select_window_id = -1; // 0x51C7FC static unsigned char* select_window_buffer = NULL; // 0x51C800 static unsigned char* monitor = NULL; // 0x51C804 static int previous_button = -1; // 0x51C808 static CacheEntry* previous_button_up_key = NULL; // 0x51C80C static CacheEntry* previous_button_down_key = NULL; // 0x51C810 static int next_button = -1; // 0x51C814 static CacheEntry* next_button_up_key = NULL; // 0x51C818 static CacheEntry* next_button_down_key = NULL; // 0x51C81C static int take_button = -1; // 0x51C820 static CacheEntry* take_button_up_key = NULL; // 0x51C824 static CacheEntry* take_button_down_key = NULL; // 0x51C828 static int modify_button = -1; // 0x51C82C static CacheEntry* modify_button_up_key = NULL; // 0x51C830 static CacheEntry* modify_button_down_key = NULL; // 0x51C834 static int create_button = -1; // 0x51C838 static CacheEntry* create_button_up_key = NULL; // 0x51C83C static CacheEntry* create_button_down_key = NULL; // 0x51C840 static int back_button = -1; // 0x51C844 static CacheEntry* back_button_up_key = NULL; // 0x51C848 static CacheEntry* back_button_down_key = NULL; // 0x667764 static unsigned char* take_button_up; // 0x667768 static unsigned char* modify_button_down; // 0x66776C static unsigned char* back_button_up; // 0x667770 static unsigned char* create_button_up; // 0x667774 static unsigned char* modify_button_up; // 0x667778 static unsigned char* back_button_down; // 0x66777C static unsigned char* create_button_down; // 0x667780 static unsigned char* take_button_down; // 0x667784 static unsigned char* next_button_down; // 0x667788 static unsigned char* next_button_up; // 0x66778C static unsigned char* previous_button_up; // 0x667790 static unsigned char* previous_button_down; // 0x4A71D0 int select_character() { if (!select_init()) { return 0; } bool cursorWasHidden = mouse_hidden(); if (cursorWasHidden) { mouse_show(); } loadColorTable("color.pal"); palette_fade_to(cmap); int rc = 0; bool done = false; while (!done) { if (game_user_wants_to_quit != 0) { break; } int keyCode = get_input(); switch (keyCode) { case KEY_MINUS: case KEY_UNDERSCORE: DecGamma(); break; case KEY_EQUAL: case KEY_PLUS: IncGamma(); break; case KEY_UPPERCASE_B: case KEY_LOWERCASE_B: case KEY_ESCAPE: rc = 3; done = true; break; case KEY_UPPERCASE_C: case KEY_LOWERCASE_C: ResetPlayer(); if (editor_design(1) == 0) { rc = 2; done = true; } else { select_update_display(); } break; case KEY_UPPERCASE_M: case KEY_LOWERCASE_M: if (!editor_design(1)) { rc = 2; done = true; } else { select_update_display(); } break; case KEY_UPPERCASE_T: case KEY_LOWERCASE_T: rc = 2; done = true; break; case KEY_F10: game_quit_with_confirm(); break; case KEY_ARROW_LEFT: gsound_play_sfx_file("ib2p1xx1"); // FALLTHROUGH case 500: premade_index -= 1; if (premade_index < 0) { premade_index = premade_total - 1; } select_update_display(); break; case KEY_ARROW_RIGHT: gsound_play_sfx_file("ib2p1xx1"); // FALLTHROUGH case 501: premade_index += 1; if (premade_index >= premade_total) { premade_index = 0; } select_update_display(); break; } } palette_fade_to(black_palette); select_exit(); if (cursorWasHidden) { mouse_hide(); } return rc; } // 0x4A7468 bool select_init() { int backgroundFid; unsigned char* backgroundFrmData; if (select_window_id != -1) { return false; } int characterSelectorWindowX = 0; int characterSelectorWindowY = 0; select_window_id = win_add(characterSelectorWindowX, characterSelectorWindowY, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, colorTable[0], 0); if (select_window_id == -1) { return select_fatal_error(false); } select_window_buffer = win_get_buf(select_window_id); if (select_window_buffer == NULL) { return select_fatal_error(false); } CacheEntry* backgroundFrmHandle; backgroundFid = art_id(OBJ_TYPE_INTERFACE, 174, 0, 0, 0); backgroundFrmData = art_ptr_lock_data(backgroundFid, 0, 0, &backgroundFrmHandle); if (backgroundFrmData == NULL) { return select_fatal_error(false); } buf_to_buf(backgroundFrmData, CS_WINDOW_WIDTH, CS_WINDOW_HEIGHT, CS_WINDOW_WIDTH, select_window_buffer, CS_WINDOW_WIDTH); monitor = (unsigned char*)mem_malloc(CS_WINDOW_BACKGROUND_WIDTH * CS_WINDOW_BACKGROUND_HEIGHT); if (monitor == NULL) return select_fatal_error(false); buf_to_buf(backgroundFrmData + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X, CS_WINDOW_BACKGROUND_WIDTH, CS_WINDOW_BACKGROUND_HEIGHT, CS_WINDOW_WIDTH, monitor, CS_WINDOW_BACKGROUND_WIDTH); art_ptr_unlock(backgroundFrmHandle); int fid; // Setup "Previous" button. fid = art_id(OBJ_TYPE_INTERFACE, 122, 0, 0, 0); previous_button_up = art_ptr_lock_data(fid, 0, 0, &previous_button_up_key); if (previous_button_up == NULL) { return select_fatal_error(false); } fid = art_id(OBJ_TYPE_INTERFACE, 123, 0, 0, 0); previous_button_down = art_ptr_lock_data(fid, 0, 0, &previous_button_down_key); if (previous_button_down == NULL) { return select_fatal_error(false); } previous_button = win_register_button(select_window_id, CS_WINDOW_PREVIOUS_BUTTON_X, CS_WINDOW_PREVIOUS_BUTTON_Y, 20, 18, -1, -1, -1, 500, previous_button_up, previous_button_down, NULL, 0); if (previous_button == -1) { return select_fatal_error(false); } win_register_button_sound_func(previous_button, gsound_med_butt_press, gsound_med_butt_release); // Setup "Next" button. fid = art_id(OBJ_TYPE_INTERFACE, 124, 0, 0, 0); next_button_up = art_ptr_lock_data(fid, 0, 0, &next_button_up_key); if (next_button_up == NULL) { return select_fatal_error(false); } fid = art_id(OBJ_TYPE_INTERFACE, 125, 0, 0, 0); next_button_down = art_ptr_lock_data(fid, 0, 0, &next_button_down_key); if (next_button_down == NULL) { return select_fatal_error(false); } next_button = win_register_button(select_window_id, CS_WINDOW_NEXT_BUTTON_X, CS_WINDOW_NEXT_BUTTON_Y, 20, 18, -1, -1, -1, 501, next_button_up, next_button_down, NULL, 0); if (next_button == -1) { return select_fatal_error(false); } win_register_button_sound_func(next_button, gsound_med_butt_press, gsound_med_butt_release); // Setup "Take" button. fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); take_button_up = art_ptr_lock_data(fid, 0, 0, &take_button_up_key); if (take_button_up == NULL) { return select_fatal_error(false); } fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); take_button_down = art_ptr_lock_data(fid, 0, 0, &take_button_down_key); if (take_button_down == NULL) { return select_fatal_error(false); } take_button = win_register_button(select_window_id, CS_WINDOW_TAKE_BUTTON_X, CS_WINDOW_TAKE_BUTTON_Y, 15, 16, -1, -1, -1, KEY_LOWERCASE_T, take_button_up, take_button_down, NULL, BUTTON_FLAG_TRANSPARENT); if (take_button == -1) { return select_fatal_error(false); } win_register_button_sound_func(take_button, gsound_red_butt_press, gsound_red_butt_release); // Setup "Modify" button. fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); modify_button_up = art_ptr_lock_data(fid, 0, 0, &modify_button_up_key); if (modify_button_up == NULL) return select_fatal_error(false); fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); modify_button_down = art_ptr_lock_data(fid, 0, 0, &modify_button_down_key); if (modify_button_down == NULL) { return select_fatal_error(false); } modify_button = win_register_button(select_window_id, CS_WINDOW_MODIFY_BUTTON_X, CS_WINDOW_MODIFY_BUTTON_Y, 15, 16, -1, -1, -1, KEY_LOWERCASE_M, modify_button_up, modify_button_down, NULL, BUTTON_FLAG_TRANSPARENT); if (modify_button == -1) { return select_fatal_error(false); } win_register_button_sound_func(modify_button, gsound_red_butt_press, gsound_red_butt_release); // Setup "Create" button. fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); create_button_up = art_ptr_lock_data(fid, 0, 0, &create_button_up_key); if (create_button_up == NULL) { return select_fatal_error(false); } fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); create_button_down = art_ptr_lock_data(fid, 0, 0, &create_button_down_key); if (create_button_down == NULL) { return select_fatal_error(false); } create_button = win_register_button(select_window_id, CS_WINDOW_CREATE_BUTTON_X, CS_WINDOW_CREATE_BUTTON_Y, 15, 16, -1, -1, -1, KEY_LOWERCASE_C, create_button_up, create_button_down, NULL, BUTTON_FLAG_TRANSPARENT); if (create_button == -1) { return select_fatal_error(false); } win_register_button_sound_func(create_button, gsound_red_butt_press, gsound_red_butt_release); // Setup "Back" button. fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); back_button_up = art_ptr_lock_data(fid, 0, 0, &back_button_up_key); if (back_button_up == NULL) { return select_fatal_error(false); } fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); back_button_down = art_ptr_lock_data(fid, 0, 0, &back_button_down_key); if (back_button_down == NULL) { return select_fatal_error(false); } back_button = win_register_button(select_window_id, CS_WINDOW_BACK_BUTTON_X, CS_WINDOW_BACK_BUTTON_Y, 15, 16, -1, -1, -1, KEY_ESCAPE, back_button_up, back_button_down, NULL, BUTTON_FLAG_TRANSPARENT); if (back_button == -1) { return select_fatal_error(false); } win_register_button_sound_func(back_button, gsound_red_butt_press, gsound_red_butt_release); premade_index = PREMADE_CHARACTER_NARG; win_draw(select_window_id); if (!select_update_display()) { return select_fatal_error(false); } return true; } // 0x4A7AD4 static void select_exit() { if (select_window_id == -1) { return; } if (previous_button != -1) { win_delete_button(previous_button); previous_button = -1; } if (previous_button_down != NULL) { art_ptr_unlock(previous_button_down_key); previous_button_down_key = NULL; previous_button_down = NULL; } if (previous_button_up != NULL) { art_ptr_unlock(previous_button_up_key); previous_button_up_key = NULL; previous_button_up = NULL; } if (next_button != -1) { win_delete_button(next_button); next_button = -1; } if (next_button_down != NULL) { art_ptr_unlock(next_button_down_key); next_button_down_key = NULL; next_button_down = NULL; } if (next_button_up != NULL) { art_ptr_unlock(next_button_up_key); next_button_up_key = NULL; next_button_up = NULL; } if (take_button != -1) { win_delete_button(take_button); take_button = -1; } if (take_button_down != NULL) { art_ptr_unlock(take_button_down_key); take_button_down_key = NULL; take_button_down = NULL; } if (take_button_up != NULL) { art_ptr_unlock(take_button_up_key); take_button_up_key = NULL; take_button_up = NULL; } if (modify_button != -1) { win_delete_button(modify_button); modify_button = -1; } if (modify_button_down != NULL) { art_ptr_unlock(modify_button_down_key); modify_button_down_key = NULL; modify_button_down = NULL; } if (modify_button_up != NULL) { art_ptr_unlock(modify_button_up_key); modify_button_up_key = NULL; modify_button_up = NULL; } if (create_button != -1) { win_delete_button(create_button); create_button = -1; } if (create_button_down != NULL) { art_ptr_unlock(create_button_down_key); create_button_down_key = NULL; create_button_down = NULL; } if (create_button_up != NULL) { art_ptr_unlock(create_button_up_key); create_button_up_key = NULL; create_button_up = NULL; } if (back_button != -1) { win_delete_button(back_button); back_button = -1; } if (back_button_down != NULL) { art_ptr_unlock(back_button_down_key); back_button_down_key = NULL; back_button_down = NULL; } if (back_button_up != NULL) { art_ptr_unlock(back_button_up_key); back_button_up_key = NULL; back_button_up = NULL; } if (monitor != NULL) { mem_free(monitor); monitor = NULL; } win_delete(select_window_id); select_window_id = -1; } // 0x4A7D58 static bool select_update_display() { char path[FILENAME_MAX]; sprintf(path, "%s.gcd", premade_characters[premade_index].fileName); if (proto_dude_init(path) == -1) { debug_printf("\n ** Error in dude init! **\n"); return false; } buf_to_buf(monitor, CS_WINDOW_BACKGROUND_WIDTH, CS_WINDOW_BACKGROUND_HEIGHT, CS_WINDOW_BACKGROUND_WIDTH, select_window_buffer + CS_WINDOW_WIDTH * CS_WINDOW_BACKGROUND_Y + CS_WINDOW_BACKGROUND_X, CS_WINDOW_WIDTH); bool success = false; if (select_display_portrait()) { if (select_display_stats()) { success = select_display_bio(); } } win_draw(select_window_id); return success; } // 0x4A7E08 static bool select_display_portrait() { bool success = false; CacheEntry* faceFrmHandle; int faceFid = art_id(OBJ_TYPE_INTERFACE, premade_characters[premade_index].face, 0, 0, 0); Art* frm = art_ptr_lock(faceFid, &faceFrmHandle); if (frm != NULL) { unsigned char* data = art_frame_data(frm, 0, 0); if (data != NULL) { int width = art_frame_width(frm, 0, 0); int height = art_frame_length(frm, 0, 0); trans_buf_to_buf(data, width, height, width, (select_window_buffer + CS_WINDOW_WIDTH * 23 + 27), CS_WINDOW_WIDTH); success = true; } art_ptr_unlock(faceFrmHandle); } return success; } // 0x4A7EA8 static bool select_display_stats() { char* str; char text[260]; int length; int value; MessageListItem messageListItem; int oldFont = text_curr(); text_font(101); text_char_width(0x20); int vh = text_height(); int y = 40; // NAME str = object_name(obj_dude); strcpy(text, str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_NAME_MID_X - (length / 2), text, 160, CS_WINDOW_WIDTH, colorTable[992]); // STRENGTH y += vh + vh + vh; value = critterGetStat(obj_dude, STAT_STRENGTH); str = stat_name(STAT_STRENGTH); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // PERCEPTION y += vh; value = critterGetStat(obj_dude, STAT_PERCEPTION); str = stat_name(STAT_PERCEPTION); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // ENDURANCE y += vh; value = critterGetStat(obj_dude, STAT_ENDURANCE); str = stat_name(STAT_PERCEPTION); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // CHARISMA y += vh; value = critterGetStat(obj_dude, STAT_CHARISMA); str = stat_name(STAT_CHARISMA); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // INTELLIGENCE y += vh; value = critterGetStat(obj_dude, STAT_INTELLIGENCE); str = stat_name(STAT_INTELLIGENCE); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // AGILITY y += vh; value = critterGetStat(obj_dude, STAT_AGILITY); str = stat_name(STAT_AGILITY); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // LUCK y += vh; value = critterGetStat(obj_dude, STAT_LUCK); str = stat_name(STAT_LUCK); sprintf(text, "%s %02d", str, value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); str = stat_level_description(value); sprintf(text, " %s", str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_PRIMARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); y += vh; // blank line // HIT POINTS y += vh; messageListItem.num = 16; text[0] = '\0'; if (message_search(&misc_message_file, &messageListItem)) { strcpy(text, messageListItem.text); } length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); value = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); sprintf(text, " %d/%d", critter_get_hits(obj_dude), value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // ARMOR CLASS y += vh; str = stat_name(STAT_ARMOR_CLASS); strcpy(text, str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); value = critterGetStat(obj_dude, STAT_ARMOR_CLASS); sprintf(text, " %d", value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // ACTION POINTS y += vh; messageListItem.num = 15; text[0] = '\0'; if (message_search(&misc_message_file, &messageListItem)) { strcpy(text, messageListItem.text); } length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); value = critterGetStat(obj_dude, STAT_MAXIMUM_ACTION_POINTS); sprintf(text, " %d", value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); // MELEE DAMAGE y += vh; str = stat_name(STAT_ARMOR_CLASS); strcpy(text, str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); value = critterGetStat(obj_dude, STAT_ARMOR_CLASS); sprintf(text, " %d", value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); y += vh; // blank line // SKILLS int skills[DEFAULT_TAGGED_SKILLS]; skill_get_tags(skills, DEFAULT_TAGGED_SKILLS); for (int index = 0; index < DEFAULT_TAGGED_SKILLS; index++) { y += vh; str = skill_name(skills[index]); strcpy(text, str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); value = skill_level(obj_dude, skills[index]); sprintf(text, " %d%%", value); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X, text, length, CS_WINDOW_WIDTH, colorTable[992]); } // TRAITS int traits[TRAITS_MAX_SELECTED_COUNT]; trait_get(&(traits[0]), &(traits[1])); for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) { y += vh; str = trait_name(traits[index]); strcpy(text, str); length = text_width(text); text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_SECONDARY_STAT_MID_X - length, text, length, CS_WINDOW_WIDTH, colorTable[992]); } text_font(oldFont); return true; } // 0x4A8AE4 static bool select_display_bio() { int oldFont = text_curr(); text_font(101); char path[FILENAME_MAX]; sprintf(path, "%s.bio", premade_characters[premade_index].fileName); File* stream = db_fopen(path, "rt"); if (stream != NULL) { int y = 40; int lineHeight = text_height(); char string[256]; while (db_fgets(string, 256, stream) && y < 260) { text_to_buf(select_window_buffer + CS_WINDOW_WIDTH * y + CS_WINDOW_BIO_X, string, CS_WINDOW_WIDTH - CS_WINDOW_BIO_X, CS_WINDOW_WIDTH, colorTable[992]); y += lineHeight; } db_fclose(stream); } text_font(oldFont); return true; } // NOTE: Inlined. // // 0x4A8BD0 static bool select_fatal_error(bool rc) { select_exit(); return rc; } ================================================ FILE: src/game/select.h ================================================ #ifndef FALLOUT_GAME_SELECT_H_ #define FALLOUT_GAME_SELECT_H_ #include extern int select_window_id; int select_character(); bool select_init(); #endif /* FALLOUT_GAME_SELECT_H_ */ ================================================ FILE: src/game/selfrun.c ================================================ #include "game/selfrun.h" #include #include #include "plib/gnw/input.h" #include "plib/db/db.h" #include "game/game.h" #include "game/gconfig.h" #include "plib/gnw/vcr.h" typedef enum SelfrunState { SELFRUN_STATE_TURNED_OFF, SELFRUN_STATE_PLAYING, SELFRUN_STATE_RECORDING, } SelfrunState; static void selfrun_playback_callback(int reason); static int selfrun_load_data(const char* path, SelfrunData* selfrunData); static int selfrun_save_data(const char* path, SelfrunData* selfrunData); // 0x51C8D8 static int selfrun_state = SELFRUN_STATE_TURNED_OFF; // 0x4A8BE0 int selfrun_get_list(char*** fileListPtr, int* fileListLengthPtr) { if (fileListPtr == NULL) { return -1; } if (fileListLengthPtr == NULL) { return -1; } *fileListLengthPtr = db_get_file_list("selfrun\\*.sdf", fileListPtr, 0, 0); return 0; } // 0x4A8C10 int selfrun_free_list(char*** fileListPtr) { if (fileListPtr == NULL) { return -1; } db_free_file_list(fileListPtr, 0); return 0; } // 0x4A8C28 int selfrun_prep_playback(const char* fileName, SelfrunData* selfrunData) { if (fileName == NULL) { return -1; } if (selfrunData == NULL) { return -1; } if (vcr_status() != VCR_STATE_TURNED_OFF) { return -1; } if (selfrun_state != SELFRUN_STATE_TURNED_OFF) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", "selfrun\\", fileName); if (selfrun_load_data(path, selfrunData) != 0) { return -1; } selfrun_state = SELFRUN_STATE_PLAYING; return 0; } // 0x4A8C88 void selfrun_playback_loop(SelfrunData* selfrunData) { if (selfrun_state == SELFRUN_STATE_PLAYING) { char path[MAX_PATH]; sprintf(path, "%s%s", "selfrun\\", selfrunData->recordingFileName); if (vcr_play(path, VCR_TERMINATE_ON_KEY_PRESS | VCR_TERMINATE_ON_MOUSE_PRESS, selfrun_playback_callback)) { bool cursorWasHidden = mouse_hidden(); if (cursorWasHidden) { mouse_show(); } while (selfrun_state == SELFRUN_STATE_PLAYING) { int keyCode = get_input(); if (keyCode != selfrunData->stopKeyCode) { game_handle_input(keyCode, false); } } while (mouse_get_buttons() != 0) { get_input(); } if (cursorWasHidden) { mouse_hide(); } } } } // 0x4A8D28 int selfrun_prep_recording(const char* recordingName, const char* mapFileName, SelfrunData* selfrunData) { if (recordingName == NULL) { return -1; } if (mapFileName == NULL) { return -1; } if (vcr_status() != VCR_STATE_TURNED_OFF) { return -1; } if (selfrun_state != SELFRUN_STATE_TURNED_OFF) { return -1; } sprintf(selfrunData->recordingFileName, "%s%s", recordingName, ".vcr"); strcpy(selfrunData->mapFileName, mapFileName); selfrunData->stopKeyCode = KEY_CTRL_R; char path[MAX_PATH]; sprintf(path, "%s%s%s", "selfrun\\", recordingName, ".sdf"); if (selfrun_save_data(path, selfrunData) != 0) { return -1; } selfrun_state = SELFRUN_STATE_RECORDING; return 0; } // 0x4A8DDC void selfrun_recording_loop(SelfrunData* selfrunData) { if (selfrun_state == SELFRUN_STATE_RECORDING) { char path[MAX_PATH]; sprintf(path, "%s%s", "selfrun\\", selfrunData->recordingFileName); if (vcr_record(path)) { if (!mouse_hidden()) { mouse_show(); } bool done = false; while (!done) { int keyCode = get_input(); if (keyCode == selfrunData->stopKeyCode) { vcr_stop(); game_user_wants_to_quit = 2; done = true; } else { game_handle_input(keyCode, false); } } } selfrun_state = SELFRUN_STATE_TURNED_OFF; } } // 0x4A8E74 static void selfrun_playback_callback(int reason) { game_user_wants_to_quit = 2; selfrun_state = SELFRUN_STATE_TURNED_OFF; } // 0x4A8E8C static int selfrun_load_data(const char* path, SelfrunData* selfrunData) { if (path == NULL) { return -1; } if (selfrunData == NULL) { return -1; } File* stream = db_fopen(path, "rb"); if (stream == NULL) { return -1; } int rc = -1; if (db_freadByteCount(stream, selfrunData->recordingFileName, SELFRUN_RECORDING_FILE_NAME_LENGTH) == 0 && db_freadByteCount(stream, selfrunData->mapFileName, SELFRUN_MAP_FILE_NAME_LENGTH) == 0 && db_freadInt(stream, &(selfrunData->stopKeyCode)) == 0) { rc = 0; } db_fclose(stream); return rc; } // 0x4A8EF4 static int selfrun_save_data(const char* path, SelfrunData* selfrunData) { if (path == NULL) { return -1; } if (selfrunData == NULL) { return -1; } char* masterPatches; config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_MASTER_PATCHES_KEY, &masterPatches); char selfrunDirectoryPath[MAX_PATH]; sprintf(selfrunDirectoryPath, "%s\\%s", masterPatches, "selfrun\\"); mkdir(selfrunDirectoryPath); File* stream = db_fopen(path, "wb"); if (stream == NULL) { return -1; } int rc = -1; if (db_fwriteByteCount(stream, selfrunData->recordingFileName, SELFRUN_RECORDING_FILE_NAME_LENGTH) == 0 && db_fwriteByteCount(stream, selfrunData->mapFileName, SELFRUN_MAP_FILE_NAME_LENGTH) == 0 && db_fwriteInt(stream, selfrunData->stopKeyCode) == 0) { rc = 0; } db_fclose(stream); return rc; } ================================================ FILE: src/game/selfrun.h ================================================ #ifndef FALLOUT_GAME_SELFRUN_H_ #define FALLOUT_GAME_SELFRUN_H_ #define SELFRUN_RECORDING_FILE_NAME_LENGTH 13 #define SELFRUN_MAP_FILE_NAME_LENGTH 13 typedef struct SelfrunData { char recordingFileName[SELFRUN_RECORDING_FILE_NAME_LENGTH]; char mapFileName[SELFRUN_MAP_FILE_NAME_LENGTH]; int stopKeyCode; } SelfrunData; static_assert(sizeof(SelfrunData) == 32, "wrong size"); int selfrun_get_list(char*** fileListPtr, int* fileListLengthPtr); int selfrun_free_list(char*** fileListPtr); int selfrun_prep_playback(const char* fileName, SelfrunData* selfrunData); void selfrun_playback_loop(SelfrunData* selfrunData); int selfrun_prep_recording(const char* recordingName, const char* mapFileName, SelfrunData* selfrunData); void selfrun_recording_loop(SelfrunData* selfrunData); #endif /* FALLOUT_GAME_SELFRUN_H_ */ ================================================ FILE: src/game/sfxcache.c ================================================ #include "game/sfxcache.h" #include #include #include #include #include #include #include "plib/db/db.h" #include "game/cache.h" #include "game/gconfig.h" #include "plib/gnw/memory.h" #include "sound_decoder.h" #include "game/sfxlist.h" #define SOUND_EFFECTS_CACHE_MIN_SIZE 0x40000 typedef struct SoundEffect { // NOTE: This field is only 1 byte, likely unsigned char. It always uses // cmp for checking implying it's not bitwise flags. Therefore it's better // to express it as boolean. bool used; CacheEntry* cacheHandle; int tag; int dataSize; int fileSize; // TODO: Make size_t. int position; int dataPosition; unsigned char* data; } SoundEffect; static_assert(sizeof(SoundEffect) == 32, "wrong size"); static int sfxc_effect_size(int tag, int* sizePtr); static int sfxc_effect_load(int tag, int* sizePtr, unsigned char* data); static void sfxc_effect_free(void* ptr); static int sfxc_handle_list_create(); static void sfxc_handle_list_destroy(); static int sfxc_handle_create(int* handlePtr, int id, void* data, CacheEntry* cacheHandle); static void sfxc_handle_destroy(int handle); static bool sfxc_handle_is_legal(int a1); static bool sfxc_mode_is_legal(int mode); static int sfxc_decode(int handle, void* buf, unsigned int size); static int sfxc_ad_reader(int handle, void* buf, unsigned int size); // 0x51C8F0 static bool sfxc_initialized = false; // 0x51C8F4 static int sfxc_cmpr = 1; // 0x51C8EC static Cache* sfxc_pcache = NULL; // 0x51C8DC static int sfxc_dlevel = INT_MAX; // 0x51C8E0 static char* sfxc_effect_path = NULL; // 0x51C8E4 static SoundEffect* sfxc_handle_list = NULL; // 0x51C8E8 static int sfxc_files_open = 0; // 0x4A8FC0 int sfxc_init(int cacheSize, const char* effectsPath) { if (!config_get_value(&game_config, GAME_CONFIG_SOUND_KEY, GAME_CONFIG_DEBUG_SFXC_KEY, &sfxc_dlevel)) { sfxc_dlevel = 1; } if (cacheSize <= SOUND_EFFECTS_CACHE_MIN_SIZE) { return -1; } if (effectsPath == NULL) { effectsPath = ""; } sfxc_effect_path = mem_strdup(effectsPath); if (sfxc_effect_path == NULL) { return -1; } if (sfxl_init(sfxc_effect_path, sfxc_cmpr, sfxc_dlevel) != SFXL_OK) { mem_free(sfxc_effect_path); return -1; } if (sfxc_handle_list_create() != 0) { sfxl_exit(); mem_free(sfxc_effect_path); return -1; } sfxc_pcache = (Cache*)mem_malloc(sizeof(*sfxc_pcache)); if (sfxc_pcache == NULL) { sfxc_handle_list_destroy(); sfxl_exit(); mem_free(sfxc_effect_path); return -1; } if (!cache_init(sfxc_pcache, sfxc_effect_size, sfxc_effect_load, sfxc_effect_free, cacheSize)) { mem_free(sfxc_pcache); sfxc_handle_list_destroy(); sfxl_exit(); mem_free(sfxc_effect_path); return -1; } sfxc_initialized = true; return 0; } // 0x4A90FC void sfxc_exit() { if (sfxc_initialized) { cache_exit(sfxc_pcache); mem_free(sfxc_pcache); sfxc_pcache = NULL; sfxc_handle_list_destroy(); sfxl_exit(); mem_free(sfxc_effect_path); sfxc_initialized = false; } } // 0x4A9140 int sfxc_is_initialized() { return sfxc_initialized; } // 0x4A9148 void sfxc_flush() { if (sfxc_initialized) { cache_flush(sfxc_pcache); } } // 0x4A915C int sfxc_cached_open(const char* fname, int mode, ...) { if (sfxc_files_open >= SOUND_EFFECTS_MAX_COUNT) { return -1; } char* copy = mem_strdup(fname); if (copy == NULL) { return -1; } int tag; int err = sfxl_name_to_tag(copy, &tag); mem_free(copy); if (err != SFXL_OK) { return -1; } void* data; CacheEntry* cacheHandle; if (!cache_lock(sfxc_pcache, tag, &data, &cacheHandle)) { return -1; } int handle; if (sfxc_handle_create(&handle, tag, data, cacheHandle) != 0) { cache_unlock(sfxc_pcache, cacheHandle); return -1; } return handle; } // 0x4A9220 int sfxc_cached_close(int handle) { if (!sfxc_handle_is_legal(handle)) { return -1; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); if (!cache_unlock(sfxc_pcache, soundEffect->cacheHandle)) { return -1; } // NOTE: Uninline. sfxc_handle_destroy(handle); return 0; } // 0x4A9274 int sfxc_cached_read(int handle, void* buf, unsigned int size) { if (!sfxc_handle_is_legal(handle)) { return -1; } if (size == 0) { return 0; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); if (soundEffect->dataSize - soundEffect->position <= 0) { return 0; } size_t bytesToRead; // NOTE: Original code uses signed comparison. if ((int)size < (soundEffect->dataSize - soundEffect->position)) { bytesToRead = size; } else { bytesToRead = soundEffect->dataSize - soundEffect->position; } switch (sfxc_cmpr) { case 0: memcpy(buf, soundEffect->data + soundEffect->position, bytesToRead); break; case 1: if (sfxc_decode(handle, buf, bytesToRead) != 0) { return -1; } break; default: return -1; } soundEffect->position += bytesToRead; return bytesToRead; } // 0x4A9350 int sfxc_cached_write(int handle, const void* buf, unsigned int size) { return -1; } // 0x4A9358 long sfxc_cached_seek(int handle, long offset, int origin) { if (!sfxc_handle_is_legal(handle)) { return -1; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); int position; switch (origin) { case SEEK_SET: position = 0; break; case SEEK_CUR: position = soundEffect->position; break; case SEEK_END: position = soundEffect->dataSize; break; default: assert(false && "Should be unreachable"); } long normalizedOffset = abs(offset); if (offset >= 0) { long remainingSize = soundEffect->dataSize - soundEffect->position; if (normalizedOffset > remainingSize) { normalizedOffset = remainingSize; } offset = position + normalizedOffset; } else { if (normalizedOffset > position) { return -1; } offset = position - normalizedOffset; } soundEffect->position = offset; return offset; } // 0x4A93F4 long sfxc_cached_tell(int handle) { if (!sfxc_handle_is_legal(handle)) { return -1; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); return soundEffect->position; } // 0x4A9418 long sfxc_cached_file_size(int handle) { if (!sfxc_handle_is_legal(handle)) { return 0; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); return soundEffect->dataSize; } // 0x4A9434 static int sfxc_effect_size(int tag, int* sizePtr) { int size; if (sfxl_size_cached(tag, &size) == -1) { return -1; } *sizePtr = size; return 0; } // 0x4A945C static int sfxc_effect_load(int tag, int* sizePtr, unsigned char* data) { if (!sfxl_tag_is_legal(tag)) { return -1; } int size; sfxl_size_cached(tag, &size); char* name; sfxl_name(tag, &name); if (db_read_to_buf(name, data)) { mem_free(name); return -1; } mem_free(name); *sizePtr = size; return 0; } // 0x4A94CC static void sfxc_effect_free(void* ptr) { mem_free(ptr); } // 0x4A94D4 static int sfxc_handle_list_create() { sfxc_handle_list = (SoundEffect*)mem_malloc(sizeof(*sfxc_handle_list) * SOUND_EFFECTS_MAX_COUNT); if (sfxc_handle_list == NULL) { return -1; } for (int index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) { SoundEffect* soundEffect = &(sfxc_handle_list[index]); soundEffect->used = false; } sfxc_files_open = 0; return 0; } // 0x4A9518 static void sfxc_handle_list_destroy() { if (sfxc_files_open) { for (int index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) { SoundEffect* soundEffect = &(sfxc_handle_list[index]); if (!soundEffect->used) { sfxc_cached_close(index); } } } mem_free(sfxc_handle_list); } // 0x4A9550 static int sfxc_handle_create(int* handlePtr, int tag, void* data, CacheEntry* cacheHandle) { if (sfxc_files_open >= SOUND_EFFECTS_MAX_COUNT) { return -1; } SoundEffect* soundEffect; int index; for (index = 0; index < SOUND_EFFECTS_MAX_COUNT; index++) { soundEffect = &(sfxc_handle_list[index]); if (!soundEffect->used) { break; } } if (index == SOUND_EFFECTS_MAX_COUNT) { return -1; } soundEffect->used = true; soundEffect->cacheHandle = cacheHandle; soundEffect->tag = tag; sfxl_size_full(tag, &(soundEffect->dataSize)); sfxl_size_cached(tag, &(soundEffect->fileSize)); soundEffect->position = 0; soundEffect->dataPosition = 0; soundEffect->data = (unsigned char*)data; *handlePtr = index; return 0; } // NOTE: Inlined. // // 0x4A9604 static void sfxc_handle_destroy(int handle) { // NOTE: There is an overflow when handle == SOUND_EFFECTS_MAX_COUNT, but // thanks to [sfxc_handle_is_legal] handle will always be less than // [SOUND_EFFECTS_MAX_COUNT]. if (handle <= SOUND_EFFECTS_MAX_COUNT) { sfxc_handle_list[handle].used = false; } } // 0x4A961C static bool sfxc_handle_is_legal(int handle) { if (handle >= SOUND_EFFECTS_MAX_COUNT) { return false; } SoundEffect* soundEffect = &sfxc_handle_list[handle]; if (!soundEffect->used) { return false; } if (soundEffect->dataSize < soundEffect->position) { return false; } return sfxl_tag_is_legal(soundEffect->tag); } // NOTE: Unused. // // 0x4A9660 static bool sfxc_mode_is_legal(int mode) { if ((mode & 0x01) != 0) return false; if ((mode & 0x02) != 0) return false; if ((mode & 0x10) != 0) return false; return true; } // 0x4A967C static int sfxc_decode(int handle, void* buf, unsigned int size) { if (!sfxc_handle_is_legal(handle)) { return -1; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); soundEffect->dataPosition = 0; int v1; int v2; int v3; SoundDecoder* soundDecoder = soundDecoderInit(sfxc_ad_reader, handle, &v1, &v2, &v3); if (soundEffect->position != 0) { void* temp = mem_malloc(soundEffect->position); if (temp == NULL) { soundDecoderFree(soundDecoder); return -1; } size_t bytesRead = soundDecoderDecode(soundDecoder, temp, soundEffect->position); mem_free(temp); if (bytesRead != soundEffect->position) { soundDecoderFree(soundDecoder); return -1; } } size_t bytesRead = soundDecoderDecode(soundDecoder, buf, size); soundDecoderFree(soundDecoder); if (bytesRead != size) { return -1; } return 0; } // 0x4A9774 static int sfxc_ad_reader(int handle, void* buf, unsigned int size) { if (size == 0) { return 0; } SoundEffect* soundEffect = &(sfxc_handle_list[handle]); unsigned int bytesToRead = soundEffect->fileSize - soundEffect->dataPosition; if (size <= bytesToRead) { bytesToRead = size; } memcpy(buf, soundEffect->data + soundEffect->dataPosition, bytesToRead); soundEffect->dataPosition += bytesToRead; return bytesToRead; } ================================================ FILE: src/game/sfxcache.h ================================================ #ifndef FALLOUT_GAME_SFXCACHE_H_ #define FALLOUT_GAME_SFXCACHE_H_ // The maximum number of sound effects that can be loaded and played // simultaneously. #define SOUND_EFFECTS_MAX_COUNT 4 int sfxc_init(int cache_size, const char* effectsPath); void sfxc_exit(); int sfxc_is_initialized(); void sfxc_flush(); int sfxc_cached_open(const char* fname, int mode, ...); int sfxc_cached_close(int handle); int sfxc_cached_read(int handle, void* buf, unsigned int size); int sfxc_cached_write(int handle, const void* buf, unsigned int size); long sfxc_cached_seek(int handle, long offset, int origin); long sfxc_cached_tell(int handle); long sfxc_cached_file_size(int handle); #endif /* FALLOUT_GAME_SFXCACHE_H_ */ ================================================ FILE: src/game/sfxlist.c ================================================ #include "game/sfxlist.h" #include #include #include #include #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "plib/gnw/memory.h" #include "sound_decoder.h" typedef struct SoundEffectsListEntry { char* name; int dataSize; int fileSize; int tag; } SoundEffectsListEntry; static int sfxl_index_to_tag(int tag, int* indexPtr); static void sfxl_destroy(); static int sfxl_get_names(); static int sfxl_copy_names(char** fileNameList); static int sfxl_get_sizes(); static int sfxl_sort_by_name(); static int sfxl_compare_by_name(const void* a1, const void* a2); static int sfxl_ad_reader(int fileHandle, void* buf, unsigned int size); // 0x51C8F8 static bool sfxl_initialized = false; // 0x51C8FC static int sfxl_dlevel = INT_MAX; // 0x51C900 static char* sfxl_effect_path = NULL; // 0x51C904 static int sfxl_effect_path_len = 0; // sndlist.lst // // 0x51C908 static SoundEffectsListEntry* sfxl_list = NULL; // The length of [sfxl_list] array. // // 0x51C90C static int sfxl_files_total = 0; // 0x667F94 static int sfxl_compression; // 0x4A98E0 bool sfxl_tag_is_legal(int a1) { return sfxl_index_to_tag(a1, NULL) == SFXL_OK; } // 0x4A98F4 int sfxl_init(const char* soundEffectsPath, int a2, int debugLevel) { char path[FILENAME_MAX]; // TODO: What for? // memcpy(path, byte_4A97E0, 0xFF); sfxl_dlevel = debugLevel; sfxl_compression = a2; sfxl_files_total = 0; sfxl_effect_path = mem_strdup(soundEffectsPath); if (sfxl_effect_path == NULL) { return SFXL_ERR; } sfxl_effect_path_len = strlen(sfxl_effect_path); if (sfxl_effect_path_len == 0 || soundEffectsPath[sfxl_effect_path_len - 1] == '\\') { sprintf(path, "%sSNDLIST.LST", soundEffectsPath); } else { sprintf(path, "%s\\SNDLIST.LST", soundEffectsPath); } File* stream = db_fopen(path, "rt"); if (stream != NULL) { db_fgets(path, 255, stream); sfxl_files_total = atoi(path); sfxl_list = (SoundEffectsListEntry*)mem_malloc(sizeof(*sfxl_list) * sfxl_files_total); for (int index = 0; index < sfxl_files_total; index++) { SoundEffectsListEntry* entry = &(sfxl_list[index]); db_fgets(path, 255, stream); // Remove trailing newline. *(path + strlen(path) - 1) = '\0'; entry->name = mem_strdup(path); db_fgets(path, 255, stream); entry->dataSize = atoi(path); db_fgets(path, 255, stream); entry->fileSize = atoi(path); db_fgets(path, 255, stream); entry->tag = atoi(path); } db_fclose(stream); debug_printf("Reading SNDLIST.LST Sound FX Count: %d", sfxl_files_total); } else { int err; err = sfxl_get_names(); if (err != SFXL_OK) { mem_free(sfxl_effect_path); return err; } err = sfxl_get_sizes(); if (err != SFXL_OK) { sfxl_destroy(); mem_free(sfxl_effect_path); return err; } // NOTE: For unknown reason tag generation functionality is missing. // You won't be able to produce the same SNDLIST.LST as the game have. // All tags will be 0 (see [sfxl_get_names]). // // On the other hand, tags read from the SNDLIST.LST are not used in // the game. Instead tag is automatically determined from entry's // index (see [sfxl_name_to_tag]). // NOTE: Uninline. sfxl_sort_by_name(); File* stream = db_fopen(path, "wt"); if (stream != NULL) { db_fprintf(stream, "%d\n", sfxl_files_total); for (int index = 0; index < sfxl_files_total; index++) { SoundEffectsListEntry* entry = &(sfxl_list[index]); db_fprintf(stream, "%s\n", entry->name); db_fprintf(stream, "%d\n", entry->dataSize); db_fprintf(stream, "%d\n", entry->fileSize); db_fprintf(stream, "%d\n", entry->tag); } db_fclose(stream); } else { debug_printf("SFXLIST: Can't open file for write %s\n", path); } } sfxl_initialized = true; return SFXL_OK; } // 0x4A9C04 void sfxl_exit() { if (sfxl_initialized) { sfxl_destroy(); mem_free(sfxl_effect_path); sfxl_initialized = false; } } // 0x4A9C28 int sfxl_name_to_tag(char* name, int* tagPtr) { if (strnicmp(sfxl_effect_path, name, sfxl_effect_path_len) != 0) { return SFXL_ERR; } SoundEffectsListEntry dummy; dummy.name = name + sfxl_effect_path_len; SoundEffectsListEntry* entry = (SoundEffectsListEntry*)bsearch(&dummy, sfxl_list, sfxl_files_total, sizeof(*sfxl_list), sfxl_compare_by_name); if (entry == NULL) { return SFXL_ERR; } int index = entry - sfxl_list; if (index < 0 || index >= sfxl_files_total) { return SFXL_ERR; } *tagPtr = 2 * index + 2; return SFXL_OK; } // 0x4A9CD8 int sfxl_name(int tag, char** pathPtr) { int index; int err = sfxl_index_to_tag(tag, &index); if (err != SFXL_OK) { return err; } char* name = sfxl_list[index].name; char* path = (char*)mem_malloc(strlen(sfxl_effect_path) + strlen(name) + 1); if (path == NULL) { return SFXL_ERR; } strcpy(path, sfxl_effect_path); strcat(path, name); *pathPtr = path; return SFXL_OK; } // 0x4A9D90 int sfxl_size_full(int tag, int* sizePtr) { int index; int rc = sfxl_index_to_tag(tag, &index); if (rc != SFXL_OK) { return rc; } SoundEffectsListEntry* entry = &(sfxl_list[index]); *sizePtr = entry->dataSize; return SFXL_OK; } // 0x4A9DBC int sfxl_size_cached(int tag, int* sizePtr) { int index; int err = sfxl_index_to_tag(tag, &index); if (err != SFXL_OK) { return err; } SoundEffectsListEntry* entry = &(sfxl_list[index]); *sizePtr = entry->fileSize; return SFXL_OK; } // 0x4A9DE8 static int sfxl_index_to_tag(int tag, int* indexPtr) { if (tag <= 0) { return SFXL_ERR_TAG_INVALID; } if ((tag & 1) != 0) { return SFXL_ERR_TAG_INVALID; } int index = (tag / 2) - 1; if (index >= sfxl_files_total) { return SFXL_ERR_TAG_INVALID; } if (indexPtr != NULL) { *indexPtr = index; } return SFXL_OK; } // 0x4A9E44 static void sfxl_destroy() { if (sfxl_files_total < 0) { return; } if (sfxl_list == NULL) { return; } for (int index = 0; index < sfxl_files_total; index++) { SoundEffectsListEntry* entry = &(sfxl_list[index]); if (entry->name != NULL) { mem_free(entry->name); } } mem_free(sfxl_list); sfxl_list = NULL; sfxl_files_total = 0; } // 0x4A9EA0 static int sfxl_get_names() { const char* extension; switch (sfxl_compression) { case 0: extension = "*.SND"; break; case 1: extension = "*.ACM"; break; default: return SFXL_ERR; } char* pattern = (char*)mem_malloc(strlen(sfxl_effect_path) + strlen(extension) + 1); if (pattern == NULL) { return SFXL_ERR; } strcpy(pattern, sfxl_effect_path); strcat(pattern, extension); char** fileNameList; sfxl_files_total = db_get_file_list(pattern, &fileNameList, 0, 0); mem_free(pattern); if (sfxl_files_total > 10000) { db_free_file_list(&fileNameList, 0); return SFXL_ERR; } if (sfxl_files_total <= 0) { return SFXL_ERR; } sfxl_list = (SoundEffectsListEntry*)mem_malloc(sizeof(*sfxl_list) * sfxl_files_total); if (sfxl_list == NULL) { db_free_file_list(&fileNameList, 0); return SFXL_ERR; } memset(sfxl_list, 0, sizeof(*sfxl_list) * sfxl_files_total); int err = sfxl_copy_names(fileNameList); db_free_file_list(&fileNameList, 0); if (err != SFXL_OK) { sfxl_destroy(); return err; } return SFXL_OK; } // 0x4AA000 static int sfxl_copy_names(char** fileNameList) { for (int index = 0; index < sfxl_files_total; index++) { SoundEffectsListEntry* entry = &(sfxl_list[index]); entry->name = mem_strdup(*fileNameList++); if (entry->name == NULL) { sfxl_destroy(); return SFXL_ERR; } } return SFXL_OK; } // 0x4AA050 static int sfxl_get_sizes() { char* path = (char*)mem_malloc(sfxl_effect_path_len + 13); if (path == NULL) { return SFXL_ERR; } strcpy(path, sfxl_effect_path); char* fileName = path + sfxl_effect_path_len; for (int index = 0; index < sfxl_files_total; index++) { SoundEffectsListEntry* entry = &(sfxl_list[index]); strcpy(fileName, entry->name); int fileSize; if (db_dir_entry(path, &fileSize) != 0) { mem_free(path); return SFXL_ERR; } if (fileSize <= 0) { mem_free(path); return SFXL_ERR; } entry->fileSize = fileSize; switch (sfxl_compression) { case 0: entry->dataSize = fileSize; break; case 1: if (1) { File* stream = db_fopen(path, "rb"); if (stream == NULL) { mem_free(path); return 1; } int v1; int v2; int v3; SoundDecoder* soundDecoder = soundDecoderInit(sfxl_ad_reader, (int)stream, &v1, &v2, &v3); entry->dataSize = 2 * v3; soundDecoderFree(soundDecoder); db_fclose(stream); } break; default: mem_free(path); return SFXL_ERR; } } mem_free(path); return SFXL_OK; } // NOTE: Inlined. // // 0x4AA200 static int sfxl_sort_by_name() { if (sfxl_files_total != 1) { qsort(sfxl_list, sfxl_files_total, sizeof(*sfxl_list), sfxl_compare_by_name); } return 0; } // 0x4AA228 static int sfxl_compare_by_name(const void* a1, const void* a2) { SoundEffectsListEntry* v1 = (SoundEffectsListEntry*)a1; SoundEffectsListEntry* v2 = (SoundEffectsListEntry*)a2; return stricmp(v1->name, v2->name); } // 0x4AA234 static int sfxl_ad_reader(int fileHandle, void* buf, unsigned int size) { return db_fread(buf, 1, size, (File*)fileHandle); } ================================================ FILE: src/game/sfxlist.h ================================================ #ifndef FALLOUT_GAME_SFXLIST_H_ #define FALLOUT_GAME_SFXLIST_H_ #include #define SFXL_OK 0 #define SFXL_ERR 1 #define SFXL_ERR_TAG_INVALID 2 bool sfxl_tag_is_legal(int tag); int sfxl_init(const char* soundEffectsPath, int a2, int debugLevel); void sfxl_exit(); int sfxl_name_to_tag(char* name, int* tagPtr); int sfxl_name(int tag, char** pathPtr); int sfxl_size_full(int tag, int* sizePtr); int sfxl_size_cached(int tag, int* sizePtr); #endif /* FALLOUT_GAME_SFXLIST_H_ */ ================================================ FILE: src/game/skill.c ================================================ #include "game/skill.h" #include #include #include "game/actions.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "game/game.h" #include "game/gconfig.h" #include "game/intface.h" #include "game/item.h" #include "game/message.h" #include "game/object.h" #include "game/palette.h" #include "game/party.h" #include "game/perk.h" #include "game/pipboy.h" #include "game/proto.h" #include "game/roll.h" #include "game/scripts.h" #include "game/stat.h" #include "game/trait.h" #define SKILLS_MAX_USES_PER_DAY 3 #define REPAIRABLE_DAMAGE_FLAGS_LENGTH 5 #define HEALABLE_DAMAGE_FLAGS_LENGTH 5 static int skillLevelCost(int a1); static void show_skill_use_messages(Object* obj, int skill, Object* a3, int a4, int a5); static int skill_game_difficulty(int skill); static int skill_use_slot_available(int skill); static int skill_use_slot_add(int skill); static int skill_use_slot_clear(); typedef struct SkillDescription { char* name; char* description; char* attributes; int frmId; int defaultValue; int statModifier; int stat1; int stat2; int field_20; int experience; int field_28; } SkillDescription; // 0x51D118 static SkillDescription skill_data[SKILL_COUNT] = { { NULL, NULL, NULL, 28, 5, 4, STAT_AGILITY, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 29, 0, 2, STAT_AGILITY, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 30, 0, 2, STAT_AGILITY, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 31, 30, 2, STAT_AGILITY, STAT_STRENGTH, 1, 0, 0 }, { NULL, NULL, NULL, 32, 20, 2, STAT_AGILITY, STAT_STRENGTH, 1, 0, 0 }, { NULL, NULL, NULL, 33, 0, 4, STAT_AGILITY, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 34, 0, 2, STAT_PERCEPTION, STAT_INTELLIGENCE, 1, 25, 0 }, { NULL, NULL, NULL, 35, 5, 1, STAT_PERCEPTION, STAT_INTELLIGENCE, 1, 50, 0 }, { NULL, NULL, NULL, 36, 5, 3, STAT_AGILITY, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 37, 10, 1, STAT_PERCEPTION, STAT_AGILITY, 1, 25, 1 }, { NULL, NULL, NULL, 38, 0, 3, STAT_AGILITY, STAT_INVALID, 1, 25, 1 }, { NULL, NULL, NULL, 39, 10, 1, STAT_PERCEPTION, STAT_AGILITY, 1, 25, 1 }, { NULL, NULL, NULL, 40, 0, 4, STAT_INTELLIGENCE, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 41, 0, 3, STAT_INTELLIGENCE, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 42, 0, 5, STAT_CHARISMA, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 43, 0, 4, STAT_CHARISMA, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 44, 0, 5, STAT_LUCK, STAT_INVALID, 1, 0, 0 }, { NULL, NULL, NULL, 45, 0, 2, STAT_ENDURANCE, STAT_INTELLIGENCE, 1, 100, 0 }, }; // 0x51D430 int gIsSteal = 0; // 0x51D434 int gStealCount = 0; // 0x51D438 int gStealSize = 0; // 0x667F98 static int timesSkillUsed[SKILL_COUNT][SKILLS_MAX_USES_PER_DAY]; // 0x668070 static int tag_skill[NUM_TAGGED_SKILLS]; // skill.msg // // 0x668080 static MessageList skill_message_file; // 0x4AA318 int skill_init() { if (!message_init(&skill_message_file)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "skill.msg"); if (!message_load(&skill_message_file, path)) { return -1; } for (int skill = 0; skill < SKILL_COUNT; skill++) { MessageListItem messageListItem; messageListItem.num = 100 + skill; if (message_search(&skill_message_file, &messageListItem)) { skill_data[skill].name = messageListItem.text; } messageListItem.num = 200 + skill; if (message_search(&skill_message_file, &messageListItem)) { skill_data[skill].description = messageListItem.text; } messageListItem.num = 300 + skill; if (message_search(&skill_message_file, &messageListItem)) { skill_data[skill].attributes = messageListItem.text; } } for (int index = 0; index < NUM_TAGGED_SKILLS; index++) { tag_skill[index] = -1; } // NOTE: Uninline. skill_use_slot_clear(); return 0; } // 0x4AA448 void skill_reset() { for (int index = 0; index < NUM_TAGGED_SKILLS; index++) { tag_skill[index] = -1; } // NOTE: Uninline. skill_use_slot_clear(); } // 0x4AA478 void skill_exit() { message_exit(&skill_message_file); } // 0x4AA488 int skill_load(File* stream) { return db_freadIntCount(stream, tag_skill, NUM_TAGGED_SKILLS); } // 0x4AA4A8 int skill_save(File* stream) { return db_fwriteIntCount(stream, tag_skill, NUM_TAGGED_SKILLS); } // 0x4AA4C8 void skill_set_defaults(CritterProtoData* data) { for (int skill = 0; skill < SKILL_COUNT; skill++) { data->skills[skill] = 0; } } // 0x4AA4E4 void skill_set_tags(int* skills, int count) { for (int index = 0; index < count; index++) { tag_skill[index] = skills[index]; } } // 0x4AA508 void skill_get_tags(int* skills, int count) { for (int index = 0; index < count; index++) { skills[index] = tag_skill[index]; } } // 0x4AA52C bool skill_is_tagged(int skill) { return skill == tag_skill[0] || skill == tag_skill[1] || skill == tag_skill[2] || skill == tag_skill[3]; } // 0x4AA558 int skill_level(Object* critter, int skill) { if (!skillIsValid(skill)) { return -5; } int baseValue = skill_points(critter, skill); if (baseValue < 0) { return baseValue; } SkillDescription* skillDescription = &(skill_data[skill]); int v7 = critterGetStat(critter, skillDescription->stat1); if (skillDescription->stat2 != -1) { v7 += critterGetStat(critter, skillDescription->stat2); } int value = skillDescription->defaultValue + skillDescription->statModifier * v7 + baseValue * skillDescription->field_20; if (critter == obj_dude) { if (skill_is_tagged(skill)) { value += baseValue * skillDescription->field_20; if (!perk_level(critter, PERK_TAG) || skill != tag_skill[3]) { value += 20; } } value += trait_adjust_skill(skill); value += perk_adjust_skill(critter, skill); value += skill_game_difficulty(skill); } if (value > 300) { value = 300; } return value; } // 0x4AA654 int skill_base(int skill) { return skillIsValid(skill) ? skill_data[skill].defaultValue : -5; } // 0x4AA680 int skill_points(Object* obj, int skill) { if (!skillIsValid(skill)) { return 0; } Proto* proto; proto_ptr(obj->pid, &proto); return proto->critter.data.skills[skill]; } // 0x4AA6BC int skill_inc_point(Object* obj, int skill) { if (obj != obj_dude) { return -5; } if (!skillIsValid(skill)) { return -5; } Proto* proto; proto_ptr(obj->pid, &proto); int unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); if (unspentSp <= 0) { return -4; } int skillValue = skill_level(obj, skill); if (skillValue >= 300) { return -3; } // NOTE: Uninline. int requiredSp = skillLevelCost(skillValue); if (unspentSp < requiredSp) { return -4; } int rc = stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, unspentSp - requiredSp); if (rc == 0) { proto->critter.data.skills[skill] += 1; } return rc; } // 0x4AA7F8 int skill_inc_point_force(Object* obj, int skill) { if (obj != obj_dude) { return -5; } if (!skillIsValid(skill)) { return -5; } Proto* proto; proto_ptr(obj->pid, &proto); if (skill_level(obj, skill) >= 300) { return -3; } proto->critter.data.skills[skill] += 1; return 0; } // Returns the cost of raising skill value in skill points. // // 0x4AA87C static int skillLevelCost(int skillValue) { if (skillValue >= 201) { return 6; } else if (skillValue >= 176) { return 5; } else if (skillValue >= 151) { return 4; } else if (skillValue >= 126) { return 3; } else if (skillValue >= 101) { return 2; } else { return 1; } } // Decrements specified skill value by one, returning appropriate amount as // unspent skill points. // // 0x4AA8C4 int skill_dec_point(Object* critter, int skill) { if (critter != obj_dude) { return -5; } if (!skillIsValid(skill)) { return -5; } Proto* proto; proto_ptr(critter->pid, &proto); if (proto->critter.data.skills[skill] <= 0) { return -2; } int unspentSp = stat_pc_get(PC_STAT_UNSPENT_SKILL_POINTS); int skillValue = skill_level(critter, skill) - 1; // NOTE: Uninline. int requiredSp = skillLevelCost(skillValue); int newUnspentSp = unspentSp + requiredSp; int rc = stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, newUnspentSp); if (rc != 0) { return rc; } proto->critter.data.skills[skill] -= 1; if (skill_is_tagged(skill)) { int oldSkillCost = skillLevelCost(skillValue); int newSkillCost = skillLevelCost(skill_level(critter, skill)); if (oldSkillCost != newSkillCost) { rc = stat_pc_set(PC_STAT_UNSPENT_SKILL_POINTS, newUnspentSp - 1); if (rc != 0) { return rc; } } } if (proto->critter.data.skills[skill] < 0) { proto->critter.data.skills[skill] = 0; } return 0; } // Decrements specified skill value by one. // // 0x4AAA34 int skill_dec_point_force(Object* obj, int skill) { Proto* proto; if (obj != obj_dude) { return -5; } if (!skillIsValid(skill)) { return -5; } proto_ptr(obj->pid, &proto); if (proto->critter.data.skills[skill] <= 0) { return -2; } proto->critter.data.skills[skill] -= 1; return 0; } // 0x4AAAA4 int skill_result(Object* critter, int skill, int modifier, int* howMuch) { if (!skillIsValid(skill)) { return ROLL_FAILURE; } if (critter == obj_dude && skill != SKILL_STEAL) { Object* partyMember = partyMemberWithHighestSkill(skill); if (partyMember != NULL) { if (partyMemberSkill(partyMember) == skill) { critter = partyMember; } } } int skillValue = skill_level(critter, skill); if (critter == obj_dude && skill == SKILL_STEAL) { if (is_pc_flag(DUDE_STATE_SNEAKING)) { if (is_pc_sneak_working()) { skillValue += 30; } } } int criticalChance = critterGetStat(critter, STAT_CRITICAL_CHANCE); return roll_check(skillValue + modifier, criticalChance, howMuch); } // NOTE: Unused. // // 0x4AAB34 int skill_contest(Object* attacker, Object* defender, int skill, int attackerModifier, int defenderModifier, int* howMuch) { int attackerRoll; int attackerHowMuch; int defenderRoll; int defenderHowMuch; attackerRoll = skill_result(attacker, skill, attackerModifier, &attackerHowMuch); if (attackerRoll > ROLL_FAILURE) { defenderRoll = skill_result(defender, skill, defenderModifier, &defenderHowMuch); if (defenderRoll > ROLL_FAILURE) { attackerHowMuch -= defenderHowMuch; } attackerRoll = roll_check_critical(attackerHowMuch, 0); } if (howMuch != NULL) { *howMuch = attackerHowMuch; } return attackerRoll; } // 0x4AAB9C char* skill_name(int skill) { return skillIsValid(skill) ? skill_data[skill].name : NULL; } // 0x4AABC0 char* skill_description(int skill) { return skillIsValid(skill) ? skill_data[skill].description : NULL; } // 0x4AABE4 char* skill_attribute(int skill) { return skillIsValid(skill) ? skill_data[skill].attributes : NULL; } // 0x4AAC08 int skill_pic(int skill) { return skillIsValid(skill) ? skill_data[skill].frmId : 0; } // 0x4AAC2C static void show_skill_use_messages(Object* obj, int skill, Object* a3, int a4, int criticalChanceModifier) { if (obj != obj_dude) { return; } if (a4 <= 0) { return; } SkillDescription* skillDescription = &(skill_data[skill]); int baseExperience = skillDescription->experience; if (baseExperience == 0) { return; } if (skillDescription->field_28 && criticalChanceModifier < 0) { baseExperience += abs(criticalChanceModifier); } int xpToAdd = a4 * baseExperience; int before = stat_pc_get(PC_STAT_EXPERIENCE); if (stat_pc_add_experience(xpToAdd) == 0 && a4 > 0) { MessageListItem messageListItem; messageListItem.num = 505; // You earn %d XP for honing your skills if (message_search(&skill_message_file, &messageListItem)) { int after = stat_pc_get(PC_STAT_EXPERIENCE); char text[60]; sprintf(text, messageListItem.text, after - before); display_print(text); } } } // skill_use // 0x4AAD08 int skill_use(Object* obj, Object* a2, int skill, int criticalChanceModifier) { MessageListItem messageListItem; char text[60]; bool giveExp = true; int currentHp = critterGetStat(a2, STAT_CURRENT_HIT_POINTS); int maximumHp = critterGetStat(a2, STAT_MAXIMUM_HIT_POINTS); int hpToHeal = 0; int maximumHpToHeal = 0; int minimumHpToHeal = 0; if (obj == obj_dude) { if (skill == SKILL_FIRST_AID || skill == SKILL_DOCTOR) { int healerRank = perk_level(obj, PERK_HEALER); minimumHpToHeal = 4 * healerRank; maximumHpToHeal = 10 * healerRank; } } int criticalChance = critterGetStat(obj, STAT_CRITICAL_CHANCE) + criticalChanceModifier; int damageHealingAttempts = 1; int v1 = 0; int v2 = 0; switch (skill) { case SKILL_FIRST_AID: if (skill_use_slot_available(SKILL_FIRST_AID) == -1) { // 590: You've taxed your ability with that skill. Wait a while. // 591: You're too tired. // 592: The strain might kill you. messageListItem.num = 590 + roll_random(0, 2); if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } if (critter_is_dead(a2)) { // 512: You can't heal the dead. // 513: Let the dead rest in peace. // 514: It's dead, get over it. messageListItem.num = 512 + roll_random(0, 2); if (message_search(&skill_message_file, &messageListItem)) { debug_printf(messageListItem.text); } break; } if (currentHp < maximumHp) { palette_fade_to(black_palette); int roll; if (critter_body_type(a2) == BODY_TYPE_ROBOTIC) { roll = ROLL_FAILURE; } else { roll = skill_result(obj, skill, criticalChance, &hpToHeal); } if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { hpToHeal = roll_random(minimumHpToHeal + 1, maximumHpToHeal + 5); critter_adjust_hits(a2, hpToHeal); if (obj == obj_dude) { // You heal %d hit points. messageListItem.num = 500; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } if (maximumHp - currentHp < hpToHeal) { hpToHeal = maximumHp - currentHp; } sprintf(text, messageListItem.text, hpToHeal); display_print(text); } a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; skill_use_slot_add(SKILL_FIRST_AID); v1 = 1; if (a2 == obj_dude) { intface_update_hit_points(true); } } else { // You fail to do any healing. messageListItem.num = 503; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } sprintf(text, messageListItem.text, hpToHeal); display_print(text); } scr_exec_map_update_scripts(); palette_fade_to(cmap); } else { if (obj == obj_dude) { // 501: You look healty already // 502: %s looks healthy already messageListItem.num = (a2 == obj_dude ? 501 : 502); if (!message_search(&skill_message_file, &messageListItem)) { return -1; } if (a2 == obj_dude) { strcpy(text, messageListItem.text); } else { sprintf(text, messageListItem.text, object_name(a2)); } display_print(text); giveExp = false; } } if (obj == obj_dude) { inc_game_time_in_seconds(1800); } break; case SKILL_DOCTOR: if (skill_use_slot_available(SKILL_DOCTOR) == -1) { // 590: You've taxed your ability with that skill. Wait a while. // 591: You're too tired. // 592: The strain might kill you. messageListItem.num = 590 + roll_random(0, 2); if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } if (critter_is_dead(a2)) { // 512: You can't heal the dead. // 513: Let the dead rest in peace. // 514: It's dead, get over it. messageListItem.num = 512 + roll_random(0, 2); if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } break; } if (currentHp < maximumHp || critter_is_crippled(a2)) { palette_fade_to(black_palette); if (critter_body_type(a2) != BODY_TYPE_ROBOTIC && critter_is_crippled(a2)) { // Damage flags which can be healed using "Doctor" skill. // // 0x4AA304 static const int flags[HEALABLE_DAMAGE_FLAGS_LENGTH] = { DAM_BLIND, DAM_CRIP_ARM_LEFT, DAM_CRIP_ARM_RIGHT, DAM_CRIP_LEG_RIGHT, DAM_CRIP_LEG_LEFT, }; for (int index = 0; index < HEALABLE_DAMAGE_FLAGS_LENGTH; index++) { if ((a2->data.critter.combat.results & flags[index]) != 0) { damageHealingAttempts++; int roll = skill_result(obj, skill, criticalChance, &hpToHeal); // 530: damaged eye // 531: crippled left arm // 532: crippled right arm // 533: crippled right leg // 534: crippled left leg messageListItem.num = 530 + index; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } MessageListItem prefix; if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { a2->data.critter.combat.results &= ~flags[index]; a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; // 520: You heal your %s. // 521: You heal the %s. prefix.num = (a2 == obj_dude ? 520 : 521); skill_use_slot_add(SKILL_DOCTOR); v1 = 1; v2 = 1; } else { // 525: You fail to heal your %s. // 526: You fail to heal the %s. prefix.num = (a2 == obj_dude ? 525 : 526); } if (!message_search(&skill_message_file, &prefix)) { return -1; } sprintf(text, prefix.text, messageListItem.text); display_print(text); show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); giveExp = false; } } } int roll; if (critter_body_type(a2) == BODY_TYPE_ROBOTIC) { roll = ROLL_FAILURE; } else { int skillValue = skill_level(obj, skill); roll = roll_check(skillValue, criticalChance, &hpToHeal); } if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { hpToHeal = roll_random(minimumHpToHeal + 4, maximumHpToHeal + 10); critter_adjust_hits(a2, hpToHeal); if (obj == obj_dude) { // You heal %d hit points. messageListItem.num = 500; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } if (maximumHp - currentHp < hpToHeal) { hpToHeal = maximumHp - currentHp; } sprintf(text, messageListItem.text, hpToHeal); display_print(text); } if (!v2) { skill_use_slot_add(SKILL_DOCTOR); } a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; if (a2 == obj_dude) { intface_update_hit_points(true); } v1 = 1; show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); scr_exec_map_update_scripts(); palette_fade_to(cmap); giveExp = false; } else { // You fail to do any healing. messageListItem.num = 503; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } sprintf(text, messageListItem.text, hpToHeal); display_print(text); scr_exec_map_update_scripts(); palette_fade_to(cmap); } } else { if (obj == obj_dude) { // 501: You look healty already // 502: %s looks healthy already messageListItem.num = (a2 == obj_dude ? 501 : 502); if (!message_search(&skill_message_file, &messageListItem)) { return -1; } if (a2 == obj_dude) { strcpy(text, messageListItem.text); } else { sprintf(text, messageListItem.text, object_name(a2)); } display_print(text); giveExp = false; } } if (obj == obj_dude) { inc_game_time_in_seconds(3600 * damageHealingAttempts); } break; case SKILL_SNEAK: case SKILL_LOCKPICK: break; case SKILL_STEAL: scripts_request_steal_container(obj, a2); break; case SKILL_TRAPS: messageListItem.num = 551; // You fail to find any traps. if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; case SKILL_SCIENCE: messageListItem.num = 552; // You fail to learn anything. if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; case SKILL_REPAIR: if (critter_body_type(a2) != BODY_TYPE_ROBOTIC) { // You cannot repair that. messageListItem.num = 553; if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } if (skill_use_slot_available(SKILL_REPAIR) == -1) { // 590: You've taxed your ability with that skill. Wait a while. // 591: You're too tired. // 592: The strain might kill you. messageListItem.num = 590 + roll_random(0, 2); if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } return -1; } if (critter_is_dead(a2)) { // You got it? messageListItem.num = 1101; if (message_search(&skill_message_file, &messageListItem)) { display_print(messageListItem.text); } break; } if (currentHp < maximumHp || critter_is_crippled(a2)) { // Damage flags which can be repaired using "Repair" skill. // // 0x4AA2F0 static const int flags[REPAIRABLE_DAMAGE_FLAGS_LENGTH] = { DAM_BLIND, DAM_CRIP_ARM_LEFT, DAM_CRIP_ARM_RIGHT, DAM_CRIP_LEG_RIGHT, DAM_CRIP_LEG_LEFT, }; palette_fade_to(black_palette); for (int index = 0; index < REPAIRABLE_DAMAGE_FLAGS_LENGTH; index++) { if ((a2->data.critter.combat.results & flags[index]) != 0) { damageHealingAttempts++; int roll = skill_result(obj, skill, criticalChance, &hpToHeal); // 530: damaged eye // 531: crippled left arm // 532: crippled right arm // 533: crippled right leg // 534: crippled left leg messageListItem.num = 530 + index; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } MessageListItem prefix; if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { a2->data.critter.combat.results &= ~flags[index]; a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; // 520: You heal your %s. // 521: You heal the %s. prefix.num = (a2 == obj_dude ? 520 : 521); skill_use_slot_add(SKILL_REPAIR); v1 = 1; v2 = 1; } else { // 525: You fail to heal your %s. // 526: You fail to heal the %s. prefix.num = (a2 == obj_dude ? 525 : 526); } if (!message_search(&skill_message_file, &prefix)) { return -1; } sprintf(text, prefix.text, messageListItem.text); display_print(text); show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); giveExp = false; } } int skillValue = skill_level(obj, skill); int roll = roll_check(skillValue, criticalChance, &hpToHeal); if (roll == ROLL_SUCCESS || roll == ROLL_CRITICAL_SUCCESS) { hpToHeal = roll_random(minimumHpToHeal + 4, maximumHpToHeal + 10); critter_adjust_hits(a2, hpToHeal); if (obj == obj_dude) { // You heal %d hit points. messageListItem.num = 500; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } if (maximumHp - currentHp < hpToHeal) { hpToHeal = maximumHp - currentHp; } sprintf(text, messageListItem.text, hpToHeal); display_print(text); } if (!v2) { skill_use_slot_add(SKILL_REPAIR); } a2->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; if (a2 == obj_dude) { intface_update_hit_points(true); } v1 = 1; show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); scr_exec_map_update_scripts(); palette_fade_to(cmap); giveExp = false; } else { // You fail to do any healing. messageListItem.num = 503; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } sprintf(text, messageListItem.text, hpToHeal); display_print(text); scr_exec_map_update_scripts(); palette_fade_to(cmap); } } else { if (obj == obj_dude) { // 501: You look healty already // 502: %s looks healthy already messageListItem.num = (a2 == obj_dude ? 501 : 502); if (!message_search(&skill_message_file, &messageListItem)) { return -1; } sprintf(text, messageListItem.text, object_name(a2)); display_print(text); giveExp = false; } } if (obj == obj_dude) { inc_game_time_in_seconds(1800 * damageHealingAttempts); } break; default: messageListItem.num = 510; // skill_use: invalid skill used. if (message_search(&skill_message_file, &messageListItem)) { debug_printf(messageListItem.text); } return -1; } if (giveExp) { show_skill_use_messages(obj, skill, a2, v1, criticalChanceModifier); } if (skill == SKILL_FIRST_AID || skill == SKILL_DOCTOR) { scr_exec_map_update_scripts(); } return 0; } // 0x4ABBE4 int skill_check_stealing(Object* a1, Object* a2, Object* item, bool isPlanting) { int howMuch; int stealModifier = gStealCount; stealModifier--; stealModifier = -stealModifier; if (a1 != obj_dude || !perkHasRank(a1, PERK_PICKPOCKET)) { // -4% per item size stealModifier -= 4 * item_size(item); if (FID_TYPE(a2->fid) == OBJ_TYPE_CRITTER) { // check facing: -25% if face to face if (is_hit_from_front(a1, a2)) { stealModifier -= 25; } } } if ((a2->data.critter.combat.results & (DAM_KNOCKED_OUT | DAM_KNOCKED_DOWN)) != 0) { stealModifier += 20; } int stealChance = stealModifier + skill_level(a1, SKILL_STEAL); if (stealChance > 95) { stealChance = 95; } int stealRoll; if (a1 == obj_dude && isPartyMember(a2)) { stealRoll = ROLL_CRITICAL_SUCCESS; } else { int criticalChance = critterGetStat(a1, STAT_CRITICAL_CHANCE); stealRoll = roll_check(stealChance, criticalChance, &howMuch); } int catchRoll; if (stealRoll == ROLL_CRITICAL_SUCCESS) { catchRoll = ROLL_CRITICAL_FAILURE; } else if (stealRoll == ROLL_CRITICAL_FAILURE) { catchRoll = ROLL_SUCCESS; } else { int catchChance; if (PID_TYPE(a2->pid) == OBJ_TYPE_CRITTER) { catchChance = skill_level(a2, SKILL_STEAL) - stealModifier; } else { catchChance = 30 - stealModifier; } catchRoll = roll_check(catchChance, 0, &howMuch); } MessageListItem messageListItem; char text[60]; if (catchRoll != ROLL_SUCCESS && catchRoll != ROLL_CRITICAL_SUCCESS) { // 571: You steal the %s. // 573: You plant the %s. messageListItem.num = isPlanting ? 573 : 571; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } sprintf(text, messageListItem.text, object_name(item)); display_print(text); return 1; } else { // 570: You're caught stealing the %s. // 572: You're caught planting the %s. messageListItem.num = isPlanting ? 572 : 570; if (!message_search(&skill_message_file, &messageListItem)) { return -1; } sprintf(text, messageListItem.text, object_name(item)); display_print(text); return 0; } } // 0x4ABDEC static int skill_game_difficulty(int skill) { switch (skill) { case SKILL_FIRST_AID: case SKILL_DOCTOR: case SKILL_SNEAK: case SKILL_LOCKPICK: case SKILL_STEAL: case SKILL_TRAPS: case SKILL_SCIENCE: case SKILL_REPAIR: case SKILL_SPEECH: case SKILL_BARTER: case SKILL_GAMBLING: case SKILL_OUTDOORSMAN: if (1) { int gameDifficulty = GAME_DIFFICULTY_NORMAL; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty); if (gameDifficulty == GAME_DIFFICULTY_HARD) { return -10; } else if (gameDifficulty == GAME_DIFFICULTY_EASY) { return 20; } } break; } return 0; } // 0x4ABE44 static int skill_use_slot_available(int skill) { for (int slot = 0; slot < SKILLS_MAX_USES_PER_DAY; slot++) { if (timesSkillUsed[skill][slot] == 0) { return slot; } } int time = game_time(); int hoursSinceLastUsage = (time - timesSkillUsed[skill][0]) / GAME_TIME_TICKS_PER_HOUR; if (hoursSinceLastUsage <= 24) { return -1; } return SKILLS_MAX_USES_PER_DAY - 1; } // 0x4ABEB8 static int skill_use_slot_add(int skill) { int slot = skill_use_slot_available(skill); if (slot == -1) { return -1; } if (timesSkillUsed[skill][slot] != 0) { for (int i = 0; i < slot; i++) { timesSkillUsed[skill][i] = timesSkillUsed[skill][i + 1]; } } timesSkillUsed[skill][slot] = game_time(); return 0; } // NOTE: Inlined. // // 0x4ABF24 static int skill_use_slot_clear() { memset(timesSkillUsed, 0, sizeof(timesSkillUsed)); return 0; } // 0x4ABF3C int skill_use_slot_save(File* stream) { return db_fwriteIntCount(stream, (int*)timesSkillUsed, SKILL_COUNT * SKILLS_MAX_USES_PER_DAY); } // 0x4ABF5C int skill_use_slot_load(File* stream) { return db_freadIntCount(stream, (int*)timesSkillUsed, SKILL_COUNT * SKILLS_MAX_USES_PER_DAY); } // 0x4ABF7C char* skillGetPartyMemberString(Object* critter, bool isDude) { int baseMessageId; int count; if (isDude) { baseMessageId = 1100; count = 4; } else { baseMessageId = 1000; count = 5; } int messageId = roll_random(0, count); MessageListItem messageListItem; char* msg = getmsg(&skill_message_file, &messageListItem, baseMessageId + messageId); return msg; } ================================================ FILE: src/game/skill.h ================================================ #ifndef FALLOUT_GAME_SKILL_H_ #define FALLOUT_GAME_SKILL_H_ #include #include "plib/db/db.h" #include "game/object_types.h" #include "game/proto_types.h" #include "game/skill_defs.h" extern int gIsSteal; extern int gStealCount; extern int gStealSize; int skill_init(); void skill_reset(); void skill_exit(); int skill_load(File* stream); int skill_save(File* stream); void skill_set_defaults(CritterProtoData* data); void skill_set_tags(int* skills, int count); void skill_get_tags(int* skills, int count); bool skill_is_tagged(int skill); int skill_level(Object* critter, int skill); int skill_base(int skill); int skill_points(Object* critter, int skill); int skill_inc_point(Object* critter, int skill); int skill_inc_point_force(Object* critter, int skill); int skill_dec_point(Object* critter, int skill); int skill_dec_point_force(Object* critter, int skill); int skill_result(Object* critter, int skill, int a3, int* a4); int skill_contest(Object* attacker, Object* defender, int skill, int attackerModifier, int defenderModifier, int* howMuch); char* skill_name(int skill); char* skill_description(int skill); char* skill_attribute(int skill); int skill_pic(int skill); int skill_use(Object* obj, Object* a2, int skill, int a4); int skill_check_stealing(Object* a1, Object* a2, Object* item, bool isPlanting); int skill_use_slot_save(File* stream); int skill_use_slot_load(File* stream); char* skillGetPartyMemberString(Object* critter, bool isDude); // Returns true if skill is valid. static inline bool skillIsValid(int skill) { return skill >= 0 && skill < SKILL_COUNT; } #endif /* FALLOUT_GAME_SKILL_H_ */ ================================================ FILE: src/game/skill_defs.h ================================================ #ifndef SKILL_DEFS_H #define SKILL_DEFS_H // max number of tagged skills #define NUM_TAGGED_SKILLS 4 #define DEFAULT_TAGGED_SKILLS 3 // Available skills. typedef enum Skill { SKILL_SMALL_GUNS, SKILL_BIG_GUNS, SKILL_ENERGY_WEAPONS, SKILL_UNARMED, SKILL_MELEE_WEAPONS, SKILL_THROWING, SKILL_FIRST_AID, SKILL_DOCTOR, SKILL_SNEAK, SKILL_LOCKPICK, SKILL_STEAL, SKILL_TRAPS, SKILL_SCIENCE, SKILL_REPAIR, SKILL_SPEECH, SKILL_BARTER, SKILL_GAMBLING, SKILL_OUTDOORSMAN, SKILL_COUNT, } Skill; #endif /* SKILL_DEFS_H */ ================================================ FILE: src/game/skilldex.c ================================================ #include "game/skilldex.h" #include #include #include #include "game/art.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "game/cycle.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gmouse.h" #include "game/gsound.h" #include "game/intface.h" #include "game/map.h" #include "game/message.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/skill.h" #include "plib/gnw/rect.h" #include "plib/gnw/text.h" #include "plib/gnw/button.h" #include "plib/gnw/gnw.h" #define SKILLDEX_WINDOW_RIGHT_MARGIN 4 #define SKILLDEX_WINDOW_BOTTOM_MARGIN 6 #define SKILLDEX_SKILL_BUTTON_BUFFER_COUNT (SKILLDEX_SKILL_COUNT * 2) typedef enum SkilldexFrm { SKILLDEX_FRM_BACKGROUND, SKILLDEX_FRM_BUTTON_ON, SKILLDEX_FRM_BUTTON_OFF, SKILLDEX_FRM_LITTLE_RED_BUTTON_UP, SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN, SKILLDEX_FRM_BIG_NUMBERS, SKILLDEX_FRM_COUNT, } SkilldexFrm; typedef enum SkilldexSkill { SKILLDEX_SKILL_SNEAK, SKILLDEX_SKILL_LOCKPICK, SKILLDEX_SKILL_STEAL, SKILLDEX_SKILL_TRAPS, SKILLDEX_SKILL_FIRST_AID, SKILLDEX_SKILL_DOCTOR, SKILLDEX_SKILL_SCIENCE, SKILLDEX_SKILL_REPAIR, SKILLDEX_SKILL_COUNT, } SkilldexSkill; static int skilldex_start(); static void skilldex_end(); // 0x51D43C static bool bk_enable = false; // 0x51D440 static int grphfid[SKILLDEX_FRM_COUNT] = { 121, 119, 120, 8, 9, 170, }; // Maps Skilldex options into skills. // // 0x51D458 static int sklxref[SKILLDEX_SKILL_COUNT] = { SKILL_SNEAK, SKILL_LOCKPICK, SKILL_STEAL, SKILL_TRAPS, SKILL_FIRST_AID, SKILL_DOCTOR, SKILL_SCIENCE, SKILL_REPAIR, }; // 0x668088 static Size ginfo[SKILLDEX_FRM_COUNT]; // 0x6680B8 static unsigned char* skldxbtn[SKILLDEX_SKILL_BUTTON_BUFFER_COUNT]; // skilldex.msg // // 0x6680F8 static MessageList skldxmsg; // 0x668100 static MessageListItem mesg; // 0x668110 static unsigned char* skldxbmp[SKILLDEX_FRM_COUNT]; // 0x668128 static CacheEntry* grphkey[SKILLDEX_FRM_COUNT]; // 0x668140 static int skldxwin; // 0x668144 static unsigned char* winbuf; // 0x668148 static int fontsave; // 0x4ABFD0 int skilldex_select() { if (skilldex_start() == -1) { debug_printf("\n ** Error loading skilldex dialog data! **\n"); return -1; } int rc = -1; while (rc == -1) { int keyCode = get_input(); if (keyCode == KEY_ESCAPE || keyCode == 500 || game_user_wants_to_quit != 0) { rc = 0; } else if (keyCode == KEY_RETURN) { gsound_play_sfx_file("ib1p1xx1"); rc = 0; } else if (keyCode >= 501 && keyCode <= 509) { rc = keyCode - 500; } } if (rc != 0) { block_for_tocks(1000 / 9); } skilldex_end(); return rc; } // 0x4AC054 static int skilldex_start() { fontsave = text_curr(); bk_enable = false; gmouse_3d_off(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); if (!message_init(&skldxmsg)) { return -1; } char path[FILENAME_MAX]; sprintf(path, "%s%s", msg_path, "skilldex.msg"); if (!message_load(&skldxmsg, path)) { return -1; } int frmIndex; for (frmIndex = 0; frmIndex < SKILLDEX_FRM_COUNT; frmIndex++) { int fid = art_id(OBJ_TYPE_INTERFACE, grphfid[frmIndex], 0, 0, 0); skldxbmp[frmIndex] = art_lock(fid, &(grphkey[frmIndex]), &(ginfo[frmIndex].width), &(ginfo[frmIndex].height)); if (skldxbmp[frmIndex] == NULL) { break; } } if (frmIndex < SKILLDEX_FRM_COUNT) { while (--frmIndex >= 0) { art_ptr_unlock(grphkey[frmIndex]); } message_exit(&skldxmsg); return -1; } bool cycle = false; int buttonDataIndex; for (buttonDataIndex = 0; buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; buttonDataIndex++) { skldxbtn[buttonDataIndex] = (unsigned char*)mem_malloc(ginfo[SKILLDEX_FRM_BUTTON_ON].height * ginfo[SKILLDEX_FRM_BUTTON_ON].width + 512); if (skldxbtn[buttonDataIndex] == NULL) { break; } // NOTE: Original code uses bitwise XOR. cycle = !cycle; unsigned char* data; int size; if (cycle) { size = ginfo[SKILLDEX_FRM_BUTTON_OFF].width * ginfo[SKILLDEX_FRM_BUTTON_OFF].height; data = skldxbmp[SKILLDEX_FRM_BUTTON_OFF]; } else { size = ginfo[SKILLDEX_FRM_BUTTON_ON].width * ginfo[SKILLDEX_FRM_BUTTON_ON].height; data = skldxbmp[SKILLDEX_FRM_BUTTON_ON]; } memcpy(skldxbtn[buttonDataIndex], data, size); } if (buttonDataIndex < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT) { while (--buttonDataIndex >= 0) { mem_free(skldxbtn[buttonDataIndex]); } for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } message_exit(&skldxmsg); return -1; } int skilldexWindowX = 640 - ginfo[SKILLDEX_FRM_BACKGROUND].width - SKILLDEX_WINDOW_RIGHT_MARGIN; int skilldexWindowY = 480 - INTERFACE_BAR_HEIGHT - 1 - ginfo[SKILLDEX_FRM_BACKGROUND].height - SKILLDEX_WINDOW_BOTTOM_MARGIN; skldxwin = win_add(skilldexWindowX, skilldexWindowY, ginfo[SKILLDEX_FRM_BACKGROUND].width, ginfo[SKILLDEX_FRM_BACKGROUND].height, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x02); if (skldxwin == -1) { for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) { mem_free(skldxbtn[index]); } for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } message_exit(&skldxmsg); return -1; } bk_enable = map_disable_bk_processes(); cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); winbuf = win_get_buf(skldxwin); memcpy(winbuf, skldxbmp[SKILLDEX_FRM_BACKGROUND], ginfo[SKILLDEX_FRM_BACKGROUND].width * ginfo[SKILLDEX_FRM_BACKGROUND].height); text_font(103); // Render "SKILLDEX" title. char* title = getmsg(&skldxmsg, &mesg, 100); text_to_buf(winbuf + 14 * ginfo[SKILLDEX_FRM_BACKGROUND].width + 55, title, ginfo[SKILLDEX_FRM_BACKGROUND].width, ginfo[SKILLDEX_FRM_BACKGROUND].width, colorTable[18979]); // Render skill values. int valueY = 48; for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) { int value = skill_level(obj_dude, sklxref[index]); if (value == -1) { value = 0; } int hundreds = value / 100; buf_to_buf(skldxbmp[SKILLDEX_FRM_BIG_NUMBERS] + 14 * hundreds, 14, 24, 336, winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * valueY + 110, ginfo[SKILLDEX_FRM_BACKGROUND].width); int tens = (value % 100) / 10; buf_to_buf(skldxbmp[SKILLDEX_FRM_BIG_NUMBERS] + 14 * tens, 14, 24, 336, winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * valueY + 124, ginfo[SKILLDEX_FRM_BACKGROUND].width); int ones = (value % 100) % 10; buf_to_buf(skldxbmp[SKILLDEX_FRM_BIG_NUMBERS] + 14 * ones, 14, 24, 336, winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * valueY + 138, ginfo[SKILLDEX_FRM_BACKGROUND].width); valueY += 36; } // Render skill buttons. int lineHeight = text_height(); int buttonY = 45; int nameY = ((ginfo[SKILLDEX_FRM_BUTTON_OFF].height - lineHeight) / 2) + 1; for (int index = 0; index < SKILLDEX_SKILL_COUNT; index++) { char name[MESSAGE_LIST_ITEM_FIELD_MAX_SIZE]; strcpy(name, getmsg(&skldxmsg, &mesg, 102 + index)); int nameX = ((ginfo[SKILLDEX_FRM_BUTTON_OFF].width - text_width(name)) / 2) + 1; if (nameX < 0) { nameX = 0; } text_to_buf(skldxbtn[index * 2] + ginfo[SKILLDEX_FRM_BUTTON_ON].width * nameY + nameX, name, ginfo[SKILLDEX_FRM_BUTTON_ON].width, ginfo[SKILLDEX_FRM_BUTTON_ON].width, colorTable[18979]); text_to_buf(skldxbtn[index * 2 + 1] + ginfo[SKILLDEX_FRM_BUTTON_OFF].width * nameY + nameX, name, ginfo[SKILLDEX_FRM_BUTTON_OFF].width, ginfo[SKILLDEX_FRM_BUTTON_OFF].width, colorTable[14723]); int btn = win_register_button(skldxwin, 15, buttonY, ginfo[SKILLDEX_FRM_BUTTON_OFF].width, ginfo[SKILLDEX_FRM_BUTTON_OFF].height, -1, -1, -1, 501 + index, skldxbtn[index * 2], skldxbtn[index * 2 + 1], NULL, BUTTON_FLAG_TRANSPARENT); if (btn != -1) { win_register_button_sound_func(btn, gsound_lrg_butt_press, gsound_lrg_butt_release); } buttonY += 36; } // Render "CANCEL" button. char* cancel = getmsg(&skldxmsg, &mesg, 101); text_to_buf(winbuf + ginfo[SKILLDEX_FRM_BACKGROUND].width * 337 + 72, cancel, ginfo[SKILLDEX_FRM_BACKGROUND].width, ginfo[SKILLDEX_FRM_BACKGROUND].width, colorTable[18979]); int cancelBtn = win_register_button(skldxwin, 48, 338, ginfo[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].width, ginfo[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP].height, -1, -1, -1, 500, skldxbmp[SKILLDEX_FRM_LITTLE_RED_BUTTON_UP], skldxbmp[SKILLDEX_FRM_LITTLE_RED_BUTTON_DOWN], NULL, BUTTON_FLAG_TRANSPARENT); if (cancelBtn != -1) { win_register_button_sound_func(cancelBtn, gsound_red_butt_press, gsound_red_butt_release); } win_draw(skldxwin); return 0; } // 0x4AC67C static void skilldex_end() { win_delete(skldxwin); for (int index = 0; index < SKILLDEX_SKILL_BUTTON_BUFFER_COUNT; index++) { mem_free(skldxbtn[index]); } for (int index = 0; index < SKILLDEX_FRM_COUNT; index++) { art_ptr_unlock(grphkey[index]); } message_exit(&skldxmsg); text_font(fontsave); if (bk_enable) { map_enable_bk_processes(); } cycle_enable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); } ================================================ FILE: src/game/skilldex.h ================================================ #ifndef FALLOUT_GAME_SKILLDEX_H_ #define FALLOUT_GAME_SKILLDEX_H_ typedef enum SkilldexRC { SKILLDEX_RC_ERROR = -1, SKILLDEX_RC_CANCELED, SKILLDEX_RC_SNEAK, SKILLDEX_RC_LOCKPICK, SKILLDEX_RC_STEAL, SKILLDEX_RC_TRAPS, SKILLDEX_RC_FIRST_AID, SKILLDEX_RC_DOCTOR, SKILLDEX_RC_SCIENCE, SKILLDEX_RC_REPAIR, } SkilldexRC; int skilldex_select(); #endif /* FALLOUT_GAME_SKILLDEX_H_ */ ================================================ FILE: src/game/stat.c ================================================ #include "game/stat.h" #include #include "game/combat.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/display.h" #include "game/game.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/message.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "game/perk.h" #include "game/proto.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/tile.h" #include "game/trait.h" // Provides metadata about stats. typedef struct StatDescription { char* name; char* description; int frmId; int minimumValue; int maximumValue; int defaultValue; } StatDescription; // 0x51D53C static StatDescription stat_data[STAT_COUNT] = { { NULL, NULL, 0, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 1, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 2, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 3, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 4, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 5, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 6, PRIMARY_STAT_MIN, PRIMARY_STAT_MAX, 5 }, { NULL, NULL, 10, 0, 999, 0 }, { NULL, NULL, 75, 1, 99, 0 }, { NULL, NULL, 18, 0, 999, 0 }, { NULL, NULL, 31, 0, INT_MAX, 0 }, { NULL, NULL, 32, 0, 500, 0 }, { NULL, NULL, 20, 0, 999, 0 }, { NULL, NULL, 24, 0, 60, 0 }, { NULL, NULL, 25, 0, 30, 0 }, { NULL, NULL, 26, 0, 100, 0 }, { NULL, NULL, 94, -60, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 22, 0, 90, 0 }, { NULL, NULL, 0, 0, 90, 0 }, { NULL, NULL, 0, 0, 90, 0 }, { NULL, NULL, 0, 0, 90, 0 }, { NULL, NULL, 0, 0, 90, 0 }, { NULL, NULL, 0, 0, 100, 0 }, { NULL, NULL, 0, 0, 90, 0 }, { NULL, NULL, 83, 0, 95, 0 }, { NULL, NULL, 23, 0, 95, 0 }, { NULL, NULL, 0, 16, 101, 25 }, { NULL, NULL, 0, 0, 1, 0 }, { NULL, NULL, 10, 0, 2000, 0 }, { NULL, NULL, 11, 0, 2000, 0 }, { NULL, NULL, 12, 0, 2000, 0 }, }; // 0x51D8CC static StatDescription pc_stat_data[PC_STAT_COUNT] = { { NULL, NULL, 0, 0, INT_MAX, 0 }, { NULL, NULL, 0, 1, PC_LEVEL_MAX, 1 }, { NULL, NULL, 0, 0, INT_MAX, 0 }, { NULL, NULL, 0, -20, 20, 0 }, { NULL, NULL, 0, 0, INT_MAX, 0 }, }; // 0x66817C static MessageList stat_message_file; // 0x668184 static char* level_description[PRIMARY_STAT_RANGE]; // 0x6681AC static int curr_pc_stat[PC_STAT_COUNT]; // 0x4AED70 int stat_init() { MessageListItem messageListItem; // NOTE: Uninline. stat_pc_set_defaults(); if (!message_init(&stat_message_file)) { return -1; } char path[MAX_PATH]; sprintf(path, "%s%s", msg_path, "stat.msg"); if (!message_load(&stat_message_file, path)) { return -1; } for (int stat = 0; stat < STAT_COUNT; stat++) { stat_data[stat].name = getmsg(&stat_message_file, &messageListItem, 100 + stat); stat_data[stat].description = getmsg(&stat_message_file, &messageListItem, 200 + stat); } for (int pcStat = 0; pcStat < PC_STAT_COUNT; pcStat++) { pc_stat_data[pcStat].name = getmsg(&stat_message_file, &messageListItem, 400 + pcStat); pc_stat_data[pcStat].description = getmsg(&stat_message_file, &messageListItem, 500 + pcStat); } for (int index = 0; index < PRIMARY_STAT_RANGE; index++) { level_description[index] = getmsg(&stat_message_file, &messageListItem, 301 + index); } return 0; } // 0x4AEEC0 int stat_reset() { // NOTE: Uninline. stat_pc_set_defaults(); return 0; } // 0x4AEEE4 int stat_exit() { message_exit(&stat_message_file); return 0; } // 0x4AEEF4 int stat_load(File* stream) { for (int index = 0; index < PC_STAT_COUNT; index++) { if (db_freadInt(stream, &(curr_pc_stat[index])) == -1) { return -1; } } return 0; } // 0x4AEF20 int stat_save(File* stream) { for (int index = 0; index < PC_STAT_COUNT; index++) { if (db_fwriteInt(stream, curr_pc_stat[index]) == -1) { return -1; } } return 0; } // 0x4AEF48 int critterGetStat(Object* critter, int stat) { int value; if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { value = stat_get_base(critter, stat); value += stat_get_bonus(critter, stat); switch (stat) { case STAT_PERCEPTION: if ((critter->data.critter.combat.results & DAM_BLIND) != 0) { value -= 5; } break; case STAT_MAXIMUM_ACTION_POINTS: if (1) { int remainingCarryWeight = critterGetStat(critter, STAT_CARRY_WEIGHT) - item_total_weight(critter); if (remainingCarryWeight < 0) { value -= -remainingCarryWeight / 40 + 1; } } break; case STAT_ARMOR_CLASS: if (isInCombat()) { if (combat_whose_turn() != critter) { int actionPointsMultiplier = 1; int hthEvadeBonus = 0; if (critter == obj_dude) { if (perkHasRank(obj_dude, PERK_HTH_EVADE)) { bool hasWeapon = false; Object* item2 = inven_right_hand(obj_dude); if (item2 != NULL) { if (item_get_type(item2) == ITEM_TYPE_WEAPON) { if (item_w_anim_code(item2) != WEAPON_ANIMATION_NONE) { hasWeapon = true; } } } if (!hasWeapon) { Object* item1 = inven_left_hand(obj_dude); if (item1 != NULL) { if (item_get_type(item1) == ITEM_TYPE_WEAPON) { if (item_w_anim_code(item1) != WEAPON_ANIMATION_NONE) { hasWeapon = true; } } } } if (!hasWeapon) { actionPointsMultiplier = 2; hthEvadeBonus = skill_level(obj_dude, SKILL_UNARMED) / 12; } } } value += hthEvadeBonus; value += critter->data.critter.combat.ap * actionPointsMultiplier; } } break; case STAT_AGE: value += game_time() / GAME_TIME_TICKS_PER_YEAR; break; } if (critter == obj_dude) { switch (stat) { case STAT_STRENGTH: if (perk_level(critter, PERK_GAIN_STRENGTH)) { value++; } if (perk_level(critter, PERK_ADRENALINE_RUSH)) { if (critterGetStat(critter, STAT_CURRENT_HIT_POINTS) < (critterGetStat(critter, STAT_MAXIMUM_HIT_POINTS) / 2)) { value++; } } break; case STAT_PERCEPTION: if (perk_level(critter, PERK_GAIN_PERCEPTION)) { value++; } break; case STAT_ENDURANCE: if (perk_level(critter, PERK_GAIN_ENDURANCE)) { value++; } break; case STAT_CHARISMA: if (1) { if (perk_level(critter, PERK_GAIN_CHARISMA)) { value++; } bool hasMirrorShades = false; Object* item2 = inven_right_hand(critter); if (item2 != NULL && item2->pid == PROTO_ID_MIRRORED_SHADES) { hasMirrorShades = true; } Object* item1 = inven_left_hand(critter); if (item1 != NULL && item1->pid == PROTO_ID_MIRRORED_SHADES) { hasMirrorShades = true; } if (hasMirrorShades) { value++; } } break; case STAT_INTELLIGENCE: if (perk_level(critter, PERK_GAIN_INTELLIGENCE)) { value++; } break; case STAT_AGILITY: if (perk_level(critter, PERK_GAIN_AGILITY)) { value++; } break; case STAT_LUCK: if (perk_level(critter, PERK_GAIN_LUCK)) { value++; } break; case STAT_MAXIMUM_HIT_POINTS: if (perk_level(critter, PERK_ALCOHOL_RAISED_HIT_POINTS)) { value += 2; } if (perk_level(critter, PERK_ALCOHOL_RAISED_HIT_POINTS_II)) { value += 4; } if (perk_level(critter, PERK_ALCOHOL_LOWERED_HIT_POINTS)) { value -= 2; } if (perk_level(critter, PERK_ALCOHOL_LOWERED_HIT_POINTS_II)) { value -= 4; } if (perk_level(critter, PERK_AUTODOC_RAISED_HIT_POINTS)) { value += 2; } if (perk_level(critter, PERK_AUTODOC_RAISED_HIT_POINTS_II)) { value += 4; } if (perk_level(critter, PERK_AUTODOC_LOWERED_HIT_POINTS)) { value -= 2; } if (perk_level(critter, PERK_AUTODOC_LOWERED_HIT_POINTS_II)) { value -= 4; } break; case STAT_DAMAGE_RESISTANCE: case STAT_DAMAGE_RESISTANCE_EXPLOSION: if (perk_level(critter, PERK_DERMAL_IMPACT_ARMOR)) { value += 5; } else if (perk_level(critter, PERK_DERMAL_IMPACT_ASSAULT_ENHANCEMENT)) { value += 10; } break; case STAT_DAMAGE_RESISTANCE_LASER: case STAT_DAMAGE_RESISTANCE_FIRE: case STAT_DAMAGE_RESISTANCE_PLASMA: if (perk_level(critter, PERK_PHOENIX_ARMOR_IMPLANTS)) { value += 5; } else if (perk_level(critter, PERK_PHOENIX_ASSAULT_ENHANCEMENT)) { value += 10; } break; case STAT_RADIATION_RESISTANCE: case STAT_POISON_RESISTANCE: if (perk_level(critter, PERK_VAULT_CITY_INOCULATIONS)) { value += 10; } break; } } value = min(max(value, stat_data[stat].minimumValue), stat_data[stat].maximumValue); } else { switch (stat) { case STAT_CURRENT_HIT_POINTS: value = critter_get_hits(critter); break; case STAT_CURRENT_POISON_LEVEL: value = critter_get_poison(critter); break; case STAT_CURRENT_RADIATION_LEVEL: value = critter_get_rads(critter); break; default: value = 0; break; } } return value; } // Returns base stat value (accounting for traits if critter is dude). // // 0x4AF3E0 int stat_get_base(Object* critter, int stat) { int value = stat_get_base_direct(critter, stat); if (critter == obj_dude) { value += trait_adjust_stat(stat); } return value; } // 0x4AF408 int stat_get_base_direct(Object* critter, int stat) { Proto* proto; if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { proto_ptr(critter->pid, &proto); return proto->critter.data.baseStats[stat]; } else { switch (stat) { case STAT_CURRENT_HIT_POINTS: return critter_get_hits(critter); case STAT_CURRENT_POISON_LEVEL: return critter_get_poison(critter); case STAT_CURRENT_RADIATION_LEVEL: return critter_get_rads(critter); } } return 0; } // 0x4AF474 int stat_get_bonus(Object* critter, int stat) { if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { Proto* proto; proto_ptr(critter->pid, &proto); return proto->critter.data.bonusStats[stat]; } return 0; } // 0x4AF4BC int stat_set_base(Object* critter, int stat, int value) { Proto* proto; if (!statIsValid(stat)) { return -5; } if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { if (stat > STAT_LUCK && stat <= STAT_POISON_RESISTANCE) { // Cannot change base value of derived stats. return -1; } if (critter == obj_dude) { value -= trait_adjust_stat(stat); } if (value < stat_data[stat].minimumValue) { return -2; } if (value > stat_data[stat].maximumValue) { return -3; } proto_ptr(critter->pid, &proto); proto->critter.data.baseStats[stat] = value; if (stat >= STAT_STRENGTH && stat <= STAT_LUCK) { stat_recalc_derived(critter); } return 0; } switch (stat) { case STAT_CURRENT_HIT_POINTS: return critter_adjust_hits(critter, value - critter_get_hits(critter)); case STAT_CURRENT_POISON_LEVEL: return critter_adjust_poison(critter, value - critter_get_poison(critter)); case STAT_CURRENT_RADIATION_LEVEL: return critter_adjust_rads(critter, value - critter_get_rads(critter)); } // Should be unreachable return 0; } // 0x4AF5D4 int inc_stat(Object* critter, int stat) { int value = stat_get_base_direct(critter, stat); if (critter == obj_dude) { value += trait_adjust_stat(stat); } return stat_set_base(critter, stat, value + 1); } // 0x4AF608 int dec_stat(Object* critter, int stat) { int value = stat_get_base_direct(critter, stat); if (critter == obj_dude) { value += trait_adjust_stat(stat); } return stat_set_base(critter, stat, value - 1); } // 0x4AF63C int stat_set_bonus(Object* critter, int stat, int value) { if (!statIsValid(stat)) { return -5; } if (stat >= 0 && stat < SAVEABLE_STAT_COUNT) { Proto* proto; proto_ptr(critter->pid, &proto); proto->critter.data.bonusStats[stat] = value; if (stat >= STAT_STRENGTH && stat <= STAT_LUCK) { stat_recalc_derived(critter); } return 0; } else { switch (stat) { case STAT_CURRENT_HIT_POINTS: return critter_adjust_hits(critter, value); case STAT_CURRENT_POISON_LEVEL: return critter_adjust_poison(critter, value); case STAT_CURRENT_RADIATION_LEVEL: return critter_adjust_rads(critter, value); } } // Should be unreachable return -1; } // 0x4AF6CC void stat_set_defaults(CritterProtoData* data) { for (int stat = 0; stat < SAVEABLE_STAT_COUNT; stat++) { data->baseStats[stat] = stat_data[stat].defaultValue; data->bonusStats[stat] = 0; } } // 0x4AF6FC void stat_recalc_derived(Object* critter) { int strength = critterGetStat(critter, STAT_STRENGTH); int perception = critterGetStat(critter, STAT_PERCEPTION); int endurance = critterGetStat(critter, STAT_ENDURANCE); int intelligence = critterGetStat(critter, STAT_INTELLIGENCE); int agility = critterGetStat(critter, STAT_AGILITY); int luck = critterGetStat(critter, STAT_LUCK); Proto* proto; proto_ptr(critter->pid, &proto); CritterProtoData* data = &(proto->critter.data); data->baseStats[STAT_MAXIMUM_HIT_POINTS] = stat_get_base(critter, STAT_STRENGTH) + stat_get_base(critter, STAT_ENDURANCE) * 2 + 15; data->baseStats[STAT_MAXIMUM_ACTION_POINTS] = agility / 2 + 5; data->baseStats[STAT_ARMOR_CLASS] = agility; data->baseStats[STAT_MELEE_DAMAGE] = max(strength - 5, 1); data->baseStats[STAT_CARRY_WEIGHT] = 25 * strength + 25; data->baseStats[STAT_SEQUENCE] = 2 * perception; data->baseStats[STAT_HEALING_RATE] = max(endurance / 3, 1); data->baseStats[STAT_CRITICAL_CHANCE] = luck; data->baseStats[STAT_BETTER_CRITICALS] = 0; data->baseStats[STAT_RADIATION_RESISTANCE] = 2 * endurance; data->baseStats[STAT_POISON_RESISTANCE] = 5 * endurance; } // 0x4AF854 char* stat_name(int stat) { return statIsValid(stat) ? stat_data[stat].name : NULL; } // 0x4AF898 char* stat_description(int stat) { return statIsValid(stat) ? stat_data[stat].description : NULL; } // 0x4AF8DC char* stat_level_description(int value) { if (value < PRIMARY_STAT_MIN) { value = PRIMARY_STAT_MIN; } else if (value > PRIMARY_STAT_MAX) { value = PRIMARY_STAT_MAX; } return level_description[value - PRIMARY_STAT_MIN]; } // 0x4AF8FC int stat_pc_get(int pcStat) { return pcStatIsValid(pcStat) ? curr_pc_stat[pcStat] : 0; } // 0x4AF910 int stat_pc_set(int pcStat, int value) { int result; if (!pcStatIsValid(pcStat)) { return -5; } if (value < pc_stat_data[pcStat].minimumValue) { return -2; } if (value > pc_stat_data[pcStat].maximumValue) { return -3; } if (pcStat != PC_STAT_EXPERIENCE || value >= curr_pc_stat[PC_STAT_EXPERIENCE]) { curr_pc_stat[pcStat] = value; if (pcStat == PC_STAT_EXPERIENCE) { result = statPCAddExperienceCheckPMs(0, true); } else { result = 0; } } else { result = statPcResetExperience(value); } return result; } // Reset stats. // // 0x4AF980 void stat_pc_set_defaults() { for (int pcStat = 0; pcStat < PC_STAT_COUNT; pcStat++) { curr_pc_stat[pcStat] = pc_stat_data[pcStat].defaultValue; } } // Returns experience to reach next level. // // 0x4AF9A0 int stat_pc_min_exp() { return statPcMinExpForLevel(curr_pc_stat[PC_STAT_LEVEL] + 1); } // Returns exp to reach given level. // // 0x4AF9A8 int statPcMinExpForLevel(int level) { if (level >= PC_LEVEL_MAX) { return -1; } int v1 = level / 2; if ((level & 1) != 0) { return 1000 * v1 * level; } else { return 1000 * v1 * (level - 1); } } // 0x4AF9F4 char* stat_pc_name(int pcStat) { return pcStat >= 0 && pcStat < PC_STAT_COUNT ? pc_stat_data[pcStat].name : NULL; } // 0x4AFA14 char* stat_pc_description(int pcStat) { return pcStat >= 0 && pcStat < PC_STAT_COUNT ? pc_stat_data[pcStat].description : NULL; } // 0x4AFA34 int stat_picture(int stat) { return statIsValid(stat) ? stat_data[stat].frmId : 0; } // Roll D10 against specified stat. // // This function is intended to be used with one of SPECIAL stats (which are // capped at 10, hence d10), not with artitrary stat, but does not enforce it. // // An optional [modifier] can be supplied as a bonus (or penalty) to the stat's // value. // // Upon return [howMuch] will be set to difference between stat's value // (accounting for given [modifier]) and d10 roll, which can be positive (or // zero) when roll succeeds, or negative when roll fails. Set [howMuch] to // `NULL` if you're not interested in this value. // // 0x4AFA78 int stat_result(Object* critter, int stat, int modifier, int* howMuch) { int value = critterGetStat(critter, stat) + modifier; int chance = roll_random(PRIMARY_STAT_MIN, PRIMARY_STAT_MAX); if (howMuch != NULL) { *howMuch = value - chance; } if (chance <= value) { return ROLL_SUCCESS; } return ROLL_FAILURE; } // 0x4AFAA8 int stat_pc_add_experience(int xp) { return statPCAddExperienceCheckPMs(xp, true); } // 0x4AFAB8 int statPCAddExperienceCheckPMs(int xp, bool a2) { int newXp = curr_pc_stat[PC_STAT_EXPERIENCE]; newXp += xp; newXp += perk_level(obj_dude, PERK_SWIFT_LEARNER) * 5 * xp / 100; if (newXp < pc_stat_data[PC_STAT_EXPERIENCE].minimumValue) { newXp = pc_stat_data[PC_STAT_EXPERIENCE].minimumValue; } if (newXp > pc_stat_data[PC_STAT_EXPERIENCE].maximumValue) { newXp = pc_stat_data[PC_STAT_EXPERIENCE].maximumValue; } curr_pc_stat[PC_STAT_EXPERIENCE] = newXp; while (curr_pc_stat[PC_STAT_LEVEL] < PC_LEVEL_MAX) { if (newXp < stat_pc_min_exp()) { break; } if (stat_pc_set(PC_STAT_LEVEL, curr_pc_stat[PC_STAT_LEVEL] + 1) == 0) { int maxHpBefore = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); // You have gone up a level. MessageListItem messageListItem; messageListItem.num = 600; if (message_search(&stat_message_file, &messageListItem)) { display_print(messageListItem.text); } pc_flag_on(DUDE_STATE_LEVEL_UP_AVAILABLE); gsound_play_sfx_file("levelup"); // NOTE: Uninline. int endurance = stat_get_base(obj_dude, STAT_ENDURANCE); int hpPerLevel = endurance / 2 + 2; hpPerLevel += perk_level(obj_dude, PERK_LIFEGIVER) * 4; int bonusHp = stat_get_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS); stat_set_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS, bonusHp + hpPerLevel); int maxHpAfter = critterGetStat(obj_dude, STAT_MAXIMUM_HIT_POINTS); critter_adjust_hits(obj_dude, maxHpAfter - maxHpBefore); intface_update_hit_points(false); if (a2) { partyMemberIncLevels(); } } } return 0; } // 0x4AFC38 int statPcResetExperience(int xp) { int oldLevel = curr_pc_stat[PC_STAT_LEVEL]; curr_pc_stat[PC_STAT_EXPERIENCE] = xp; int level = 1; do { level += 1; } while (xp >= statPcMinExpForLevel(level) && level < PC_LEVEL_MAX); int newLevel = level - 1; stat_pc_set(PC_STAT_LEVEL, newLevel); pc_flag_off(DUDE_STATE_LEVEL_UP_AVAILABLE); // NOTE: Uninline. int endurance = stat_get_base(obj_dude, STAT_ENDURANCE); int hpPerLevel = endurance / 2 + 2; hpPerLevel += perk_level(obj_dude, PERK_LIFEGIVER) * 4; int deltaHp = (oldLevel - newLevel) * hpPerLevel; critter_adjust_hits(obj_dude, -deltaHp); int bonusHp = stat_get_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS); stat_set_bonus(obj_dude, STAT_MAXIMUM_HIT_POINTS, bonusHp - deltaHp); intface_update_hit_points(false); return 0; } ================================================ FILE: src/game/stat.h ================================================ #ifndef FALLOUT_GAME_STAT_H_ #define FALLOUT_GAME_STAT_H_ #include #include "plib/db/db.h" #include "game/object_types.h" #include "game/proto_types.h" #include "game/stat_defs.h" #define STAT_ERR_INVALID_STAT -5 int stat_init(); int stat_reset(); int stat_exit(); int stat_load(File* stream); int stat_save(File* stream); int critterGetStat(Object* critter, int stat); int stat_get_base(Object* critter, int stat); int stat_get_base_direct(Object* critter, int stat); int stat_get_bonus(Object* critter, int stat); int stat_set_base(Object* critter, int stat, int value); int inc_stat(Object* critter, int stat); int dec_stat(Object* critter, int stat); int stat_set_bonus(Object* critter, int stat, int value); void stat_set_defaults(CritterProtoData* data); void stat_recalc_derived(Object* critter); char* stat_name(int stat); char* stat_description(int stat); char* stat_level_description(int value); int stat_pc_get(int pcStat); int stat_pc_set(int pcStat, int value); void stat_pc_set_defaults(); int stat_pc_min_exp(); int statPcMinExpForLevel(int level); char* stat_pc_name(int pcStat); char* stat_pc_description(int pcStat); int stat_picture(int stat); int stat_result(Object* critter, int stat, int modifier, int* howMuch); int stat_pc_add_experience(int xp); int statPCAddExperienceCheckPMs(int xp, bool a2); int statPcResetExperience(int a1); static inline bool statIsValid(int stat) { return stat >= 0 && stat < STAT_COUNT; } static inline bool pcStatIsValid(int pcStat) { return pcStat >= 0 && pcStat < PC_STAT_COUNT; } #endif /* FALLOUT_GAME_STAT_H_ */ ================================================ FILE: src/game/stat_defs.h ================================================ #ifndef STAT_DEFS #define STAT_DEFS // The minimum value of SPECIAL stat. #define PRIMARY_STAT_MIN (1) // The maximum value of SPECIAL stat. #define PRIMARY_STAT_MAX (10) // The number of values of SPECIAL stat. // // Every stat value has it's own human readable description. This value is used // as number of these descriptions. #define PRIMARY_STAT_RANGE ((PRIMARY_STAT_MAX) - (PRIMARY_STAT_MIN) + 1) // The maximum number of PC level. #define PC_LEVEL_MAX 99 // Available stats. typedef enum Stat { STAT_STRENGTH, STAT_PERCEPTION, STAT_ENDURANCE, STAT_CHARISMA, STAT_INTELLIGENCE, STAT_AGILITY, STAT_LUCK, STAT_MAXIMUM_HIT_POINTS, STAT_MAXIMUM_ACTION_POINTS, STAT_ARMOR_CLASS, STAT_UNARMED_DAMAGE, STAT_MELEE_DAMAGE, STAT_CARRY_WEIGHT, STAT_SEQUENCE, STAT_HEALING_RATE, STAT_CRITICAL_CHANCE, STAT_BETTER_CRITICALS, STAT_DAMAGE_THRESHOLD, STAT_DAMAGE_THRESHOLD_LASER, STAT_DAMAGE_THRESHOLD_FIRE, STAT_DAMAGE_THRESHOLD_PLASMA, STAT_DAMAGE_THRESHOLD_ELECTRICAL, STAT_DAMAGE_THRESHOLD_EMP, STAT_DAMAGE_THRESHOLD_EXPLOSION, STAT_DAMAGE_RESISTANCE, STAT_DAMAGE_RESISTANCE_LASER, STAT_DAMAGE_RESISTANCE_FIRE, STAT_DAMAGE_RESISTANCE_PLASMA, STAT_DAMAGE_RESISTANCE_ELECTRICAL, STAT_DAMAGE_RESISTANCE_EMP, STAT_DAMAGE_RESISTANCE_EXPLOSION, STAT_RADIATION_RESISTANCE, STAT_POISON_RESISTANCE, STAT_AGE, STAT_GENDER, STAT_CURRENT_HIT_POINTS, STAT_CURRENT_POISON_LEVEL, STAT_CURRENT_RADIATION_LEVEL, STAT_COUNT, // Number of primary stats. PRIMARY_STAT_COUNT = 7, // Number of SPECIAL stats (primary + secondary). SPECIAL_STAT_COUNT = 33, // Number of saveable stats (i.e. excluding CURRENT pseudostats). SAVEABLE_STAT_COUNT = 35, } Stat; #define STAT_INVALID -1 // Special stats that are only relevant to player character. typedef enum PcStat { PC_STAT_UNSPENT_SKILL_POINTS, PC_STAT_LEVEL, PC_STAT_EXPERIENCE, PC_STAT_REPUTATION, PC_STAT_KARMA, PC_STAT_COUNT, } PcStat; #endif /* STAT_DEFS */ ================================================ FILE: src/game/strparse.c ================================================ #include "game/strparse.h" #include #include #include "plib/gnw/debug.h" // strParseValue // 0x4AFD10 int strParseValue(char** stringPtr, int* valuePtr) { char *str, *remaining_str; int v1, v2, v3; char tmp; if (*stringPtr == NULL) { return 0; } str = *stringPtr; strlwr(str); v1 = strspn(str, " "); str += v1; v2 = strcspn(str, ","); v3 = v1 + v2; remaining_str = *stringPtr + v3; *stringPtr = remaining_str; if (*remaining_str != '\0') { *stringPtr = remaining_str + 1; } if (v2 != 0) { tmp = *(str + v2); *(str + v2) = '\0'; } *valuePtr = atoi(str); if (v2 != 0) { *(str + v2) = tmp; } return 0; } // strParseStrFromList // 0x4AFE08 int strParseStrFromList(char** stringPtr, int* valuePtr, const char** stringList, int stringListLength) { int i; char *str, *remaining_str; int v1, v2, v3; char tmp; if (*stringPtr == NULL) { return 0; } str = *stringPtr; strlwr(str); v1 = strspn(str, " "); str += v1; v2 = strcspn(str, ","); v3 = v1 + v2; remaining_str = *stringPtr + v3; *stringPtr = remaining_str; if (*remaining_str != '\0') { *stringPtr = remaining_str + 1; } if (v2 != 0) { tmp = *(str + v2); *(str + v2) = '\0'; } for (i = 0; i < stringListLength; i++) { if (stricmp(str, stringList[i]) == 0) { break; } } if (v2 != 0) { *(str + v2) = tmp; } if (i == stringListLength) { debug_printf("\nstrParseStrFromList Error: Couldn't find match for string: %s!", str); *valuePtr = -1; return -1; } *valuePtr = i; return 0; } // strParseStrFromFunc // 0x4AFEDC int strParseStrFromFunc(char** stringPtr, int* valuePtr, StringParserCallback* callback) { char *str, *remaining_str; int v1, v2, v3; char tmp; int result; if (*stringPtr == NULL) { return 0; } str = *stringPtr; strlwr(str); v1 = strspn(str, " "); str += v1; v2 = strcspn(str, ","); v3 = v1 + v2; remaining_str = *stringPtr + v3; *stringPtr = remaining_str; if (*remaining_str != '\0') { *stringPtr = remaining_str + 1; } if (v2 != 0) { tmp = *(str + v2); *(str + v2) = '\0'; } result = callback(str, valuePtr); if (v2 != 0) { *(str + v2) = tmp; } if (result != 0) { debug_printf("\nstrParseStrFromFunc Error: Couldn't find match for string: %s!", str); *valuePtr = -1; return -1; } return 0; } // 0x4AFF7C int strParseStrSepVal(char** stringPtr, const char* key, int* valuePtr, const char* delimeter) { char* str; int v1, v2, v3, v4, v5; char tmp1, tmp2; int result; result = -1; if (*stringPtr == NULL) { return 0; } str = *stringPtr; if (*str == '\0') { return -1; } strlwr(str); if (*str == ',') { str++; *stringPtr = *stringPtr + 1; } v1 = strspn(str, " "); str += v1; v2 = strcspn(str, ","); v3 = v1 + v2; tmp1 = *(str + v2); *(str + v2) = '\0'; v4 = strcspn(str, delimeter); v5 = v1 + v4; tmp2 = *(str + v4); *(str + v4) = '\0'; if (strcmp(str, key) == 0) { *stringPtr = *stringPtr + v3; *valuePtr = atoi(str + v4 + 1); result = 0; } *(str + v4) = tmp2; *(str + v2) = tmp1; return result; } // 0x4B005C int strParseStrAndSepVal(char** stringPtr, char* key, int* valuePtr, const char* delimiter) { char* str; int v1, v2, v3, v4, v5; char tmp1, tmp2; if (*stringPtr == NULL) { return 0; } str = *stringPtr; if (*str == '\0') { return -1; } strlwr(str); if (*str == ',') { str++; *stringPtr = *stringPtr + 1; } v1 = strspn(str, " "); str += v1; v2 = strcspn(str, ","); v3 = v1 + v2; tmp1 = *(str + v2); *(str + v2) = '\0'; v4 = strcspn(str, delimiter); v5 = v1 + v4; tmp2 = *(str + v4); *(str + v4) = '\0'; strcpy(key, str); *stringPtr = *stringPtr + v3; *valuePtr = atoi(str + v4 + 1); *(str + v4) = tmp2; *(str + v2) = tmp1; return 0; } ================================================ FILE: src/game/strparse.h ================================================ #ifndef FALLOUT_GAME_STRPARSE_H_ #define FALLOUT_GAME_STRPARSE_H_ typedef int(StringParserCallback)(char* string, int* valuePtr); int strParseValue(char** stringPtr, int* valuePtr); int strParseStrFromList(char** stringPtr, int* valuePtr, const char** list, int count); int strParseStrFromFunc(char** stringPtr, int* valuePtr, StringParserCallback* callback); int strParseStrSepVal(char** stringPtr, const char* key, int* valuePtr, const char* delimeter); int strParseStrAndSepVal(char** stringPtr, char* key, int* valuePtr, const char* delimeter); #endif /* FALLOUT_GAME_STRPARSE_H_ */ ================================================ FILE: src/game/textobj.c ================================================ #include "game/textobj.h" #include #include "plib/gnw/input.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/gconfig.h" #include "plib/gnw/memory.h" #include "game/object.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "game/wordwrap.h" // The maximum number of text objects that can exist at the same time. #define TEXT_OBJECTS_MAX_COUNT 20 typedef enum TextObjectFlags { TEXT_OBJECT_MARKED_FOR_REMOVAL = 0x01, TEXT_OBJECT_UNBOUNDED = 0x02, } TextObjectFlags; typedef struct TextObject { int flags; Object* owner; unsigned int time; int linesCount; int sx; int sy; int tile; int x; int y; int width; int height; unsigned char* data; } TextObject; static_assert(sizeof(TextObject) == 48, "wrong size"); static void text_object_bk(); static void text_object_get_offset(TextObject* textObject); // 0x51D944 static int text_object_index = 0; // 0x51D948 static unsigned int text_object_base_delay = 3500; // 0x51D94C static unsigned int text_object_line_delay = 1399; // 0x6681C0 static TextObject* text_object_list[TEXT_OBJECTS_MAX_COUNT]; // 0x668210 static int display_width; // 0x668214 static int display_height; // 0x668218 static unsigned char* display_buffer; // 0x66821C static bool text_object_enabled; // 0x668220 static bool text_object_initialized; // 0x4B0130 int text_object_init(unsigned char* windowBuffer, int width, int height) { if (text_object_initialized) { return -1; } display_buffer = windowBuffer; display_width = width; display_height = height; text_object_index = 0; add_bk_process(text_object_bk); double textBaseDelay; if (!config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_BASE_DELAY_KEY, &textBaseDelay)) { textBaseDelay = 3.5; } double textLineDelay; if (!config_get_double(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_TEXT_LINE_DELAY_KEY, &textLineDelay)) { textLineDelay = 1.399993896484375; } text_object_base_delay = (unsigned int)(textBaseDelay * 1000.0); text_object_line_delay = (unsigned int)(textLineDelay * 1000.0); text_object_enabled = true; text_object_initialized = true; return 0; } // 0x4B021C int text_object_reset() { if (!text_object_initialized) { return -1; } for (int index = 0; index < text_object_index; index++) { mem_free(text_object_list[index]->data); mem_free(text_object_list[index]); } text_object_index = 0; add_bk_process(text_object_bk); return 0; } // 0x4B0280 void text_object_exit() { if (text_object_initialized) { text_object_reset(); remove_bk_process(text_object_bk); text_object_initialized = false; } } // 0x4B02A4 void text_object_disable() { text_object_enabled = false; } // 0x4B02B0 void text_object_enable() { text_object_enabled = true; } // NOTE: Unused. // // 0x4B02BC int text_object_is_enabled() { return text_object_enabled; } // 0x4B02C4 void text_object_set_base_delay(double value) { if (value < 1.0) { value = 1.0; } text_object_base_delay = (int)(value * 1000.0); } // NOTE: Unused. // // 0x4B0308 unsigned int text_object_get_base_delay() { return text_object_base_delay / 1000; } // 0x4B031C void text_object_set_line_delay(double value) { if (value < 0.0) { value = 0.0; } text_object_line_delay = (int)(value * 1000.0); } // NOTE: Unused. // // 0x4B0358 unsigned int text_object_get_line_delay() { return text_object_line_delay / 1000; } // text_object_create // 0x4B036C int text_object_create(Object* object, char* string, int font, int color, int a5, Rect* rect) { if (!text_object_initialized) { return -1; } if (text_object_index >= TEXT_OBJECTS_MAX_COUNT - 1) { return -1; } if (string == NULL) { return -1; } if (*string == '\0') { return -1; } TextObject* textObject = (TextObject*)mem_malloc(sizeof(*textObject)); if (textObject == NULL) { return -1; } memset(textObject, 0, sizeof(*textObject)); int oldFont = text_curr(); text_font(font); short beginnings[WORD_WRAP_MAX_COUNT]; short count; if (word_wrap(string, 200, beginnings, &count) != 0) { text_font(oldFont); return -1; } textObject->linesCount = count - 1; if (textObject->linesCount < 1) { debug_printf("**Error in text_object_create()\n"); } textObject->width = 0; for (int index = 0; index < textObject->linesCount; index++) { char* ending = string + beginnings[index + 1]; char* beginning = string + beginnings[index]; if (ending[-1] == ' ') { --ending; } char c = *ending; *ending = '\0'; // NOTE: Calls [text_width] twice, probably result of using min/max macro int width = text_width(beginning); if (width >= textObject->width) { textObject->width = width; } *ending = c; } textObject->height = (text_height() + 1) * textObject->linesCount; if (a5 != -1) { textObject->width += 2; textObject->height += 2; } int size = textObject->width * textObject->height; textObject->data = (unsigned char*)mem_malloc(size); if (textObject->data == NULL) { text_font(oldFont); return -1; } memset(textObject->data, 0, size); unsigned char* dest = textObject->data; int skip = textObject->width * (text_height() + 1); if (a5 != -1) { dest += textObject->width; } for (int index = 0; index < textObject->linesCount; index++) { char* beginning = string + beginnings[index]; char* ending = string + beginnings[index + 1]; if (ending[-1] == ' ') { --ending; } char c = *ending; *ending = '\0'; int width = text_width(beginning); text_to_buf(dest + (textObject->width - width) / 2, beginning, textObject->width, textObject->width, color); *ending = c; dest += skip; } if (a5 != -1) { buf_outline(textObject->data, textObject->width, textObject->height, textObject->width, a5); } if (object != NULL) { textObject->tile = object->tile; } else { textObject->flags |= TEXT_OBJECT_UNBOUNDED; textObject->tile = tile_center_tile; } text_object_get_offset(textObject); if (rect != NULL) { rect->ulx = textObject->x; rect->uly = textObject->y; rect->lrx = textObject->x + textObject->width - 1; rect->lry = textObject->y + textObject->height - 1; } text_object_remove(object); textObject->owner = object; textObject->time = get_bk_time(); text_object_list[text_object_index] = textObject; text_object_index++; text_font(oldFont); return 0; } // 0x4B06E8 void text_object_render(Rect* rect) { if (!text_object_initialized) { return; } for (int index = 0; index < text_object_index; index++) { TextObject* textObject = text_object_list[index]; tile_coord(textObject->tile, &(textObject->x), &(textObject->y), map_elevation); textObject->x += textObject->sx; textObject->y += textObject->sy; Rect textObjectRect; textObjectRect.ulx = textObject->x; textObjectRect.uly = textObject->y; textObjectRect.lrx = textObject->width + textObject->x - 1; textObjectRect.lry = textObject->height + textObject->y - 1; if (rect_inside_bound(&textObjectRect, rect, &textObjectRect) == 0) { trans_buf_to_buf(textObject->data + textObject->width * (textObjectRect.uly - textObject->y) + (textObjectRect.ulx - textObject->x), textObjectRect.lrx - textObjectRect.ulx + 1, textObjectRect.lry - textObjectRect.uly + 1, textObject->width, display_buffer + display_width * textObjectRect.uly + textObjectRect.ulx, display_width); } } } // 0x4B07F0 int text_object_count() { return text_object_index; } // 0x4B07F8 static void text_object_bk() { if (!text_object_enabled) { return; } bool textObjectsRemoved = false; Rect dirtyRect; for (int index = 0; index < text_object_index; index++) { TextObject* textObject = text_object_list[index]; unsigned int delay = text_object_line_delay * textObject->linesCount + text_object_base_delay; if ((textObject->flags & TEXT_OBJECT_MARKED_FOR_REMOVAL) != 0 || (elapsed_tocks(get_bk_time(), textObject->time) > delay)) { tile_coord(textObject->tile, &(textObject->x), &(textObject->y), map_elevation); textObject->x += textObject->sx; textObject->y += textObject->sy; Rect textObjectRect; textObjectRect.ulx = textObject->x; textObjectRect.uly = textObject->y; textObjectRect.lrx = textObject->width + textObject->x - 1; textObjectRect.lry = textObject->height + textObject->y - 1; if (textObjectsRemoved) { rect_min_bound(&dirtyRect, &textObjectRect, &dirtyRect); } else { rectCopy(&dirtyRect, &textObjectRect); textObjectsRemoved = true; } mem_free(textObject->data); mem_free(textObject); memmove(&(text_object_list[index]), &(text_object_list[index + 1]), sizeof(*text_object_list) * (text_object_index - index - 1)); text_object_index--; index--; } } if (textObjectsRemoved) { tile_refresh_rect(&dirtyRect, map_elevation); } } // Finds best position for placing text object. // // 0x4B0954 static void text_object_get_offset(TextObject* textObject) { int tileScreenX; int tileScreenY; tile_coord(textObject->tile, &tileScreenX, &tileScreenY, map_elevation); textObject->x = tileScreenX + 16 - textObject->width / 2; textObject->y = tileScreenY; if ((textObject->flags & TEXT_OBJECT_UNBOUNDED) == 0) { textObject->y -= textObject->height + 60; } if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x -= textObject->width / 2; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x += textObject->width; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x = tileScreenX - 16 - textObject->width; textObject->y = tileScreenY - 16 - textObject->height; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x += textObject->width + 64; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x = tileScreenX + 16 - textObject->width / 2; textObject->y = tileScreenY; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x -= textObject->width / 2; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x += textObject->width; if ((textObject->x >= 0 && textObject->x + textObject->width - 1 < display_width) && (textObject->y >= 0 && textObject->y + textObject->height - 1 < display_height)) { textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; return; } textObject->x = tileScreenX + 16 - textObject->width / 2; textObject->y = tileScreenY - (textObject->height + 60); textObject->sx = textObject->x - tileScreenX; textObject->sy = textObject->y - tileScreenY; } // Marks text objects attached to [object] for removal. // // 0x4B0C00 void text_object_remove(Object* object) { for (int index = 0; index < text_object_index; index++) { if (text_object_list[index]->owner == object) { text_object_list[index]->flags |= TEXT_OBJECT_MARKED_FOR_REMOVAL; } } } ================================================ FILE: src/game/textobj.h ================================================ #ifndef FALLOUT_GAME_TEXTOBJ_H_ #define FALLOUT_GAME_TEXTOBJ_H_ #include #include "plib/gnw/rect.h" #include "game/object_types.h" int text_object_init(unsigned char* windowBuffer, int width, int height); int text_object_reset(); void text_object_exit(); void text_object_disable(); void text_object_enable(); int text_object_is_enabled(); void text_object_set_base_delay(double value); unsigned int text_object_get_base_delay(); void text_object_set_line_delay(double value); unsigned int text_object_get_line_delay(); int text_object_create(Object* object, char* string, int font, int color, int a5, Rect* rect); void text_object_render(Rect* rect); int text_object_count(); void text_object_remove(Object* object); #endif /* FALLOUT_GAME_TEXTOBJ_H_ */ ================================================ FILE: src/game/tile.c ================================================ #include "game/tile.h" #include #include #define _USE_MATH_DEFINES #include #include "plib/color/color.h" #include "game/config.h" #include "plib/gnw/input.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/light.h" #include "game/map.h" #include "game/object.h" #define TILE_IS_VALID(tile) ((tile) >= 0 && (tile) < grid_size) typedef struct STRUCT_51D99C { int field_0; int field_4; } STRUCT_51D99C; typedef struct STRUCT_51DA04 { int field_0; int field_4; } STRUCT_51DA04; typedef struct STRUCT_51DA6C { int field_0; int field_4; int field_8; int field_C; // something with light level? } STRUCT_51DA6C; typedef struct STRUCT_51DB0C { int field_0; int field_4; int field_8; } STRUCT_51DB0C; typedef struct STRUCT_51DB48 { int field_0; int field_4; int field_8; } STRUCT_51DB48; static void refresh_mapper(Rect* rect, int elevation); static void refresh_game(Rect* rect, int elevation); static bool tile_on_edge(int tile); static void roof_fill_on(int x, int y, int elevation); static void roof_fill_off(int x, int y, int elevation); static void roof_draw(int fid, int x, int y, Rect* rect, int light); // 0x51D950 static bool borderInitialized = false; // 0x51D954 static bool scroll_blocking_on = true; // 0x51D958 static bool scroll_limiting_on = true; // 0x51D95C static bool show_roof = true; // 0x51D960 static bool show_grid = false; // 0x51D964 static TileWindowRefreshElevationProc* tile_refresh = refresh_game; // 0x51D968 static bool refresh_enabled = true; // 0x51D96C int off_tile[2][6] = { { 16, 32, 16, -16, -32, -16, }, { -12, 0, 12, 12, 0, -12, } }; // 0x51D99C static STRUCT_51D99C rightside_up_table[13] = { { -1, 2 }, { 78, 2 }, { 76, 6 }, { 73, 8 }, { 71, 10 }, { 68, 14 }, { 65, 16 }, { 63, 18 }, { 61, 20 }, { 58, 24 }, { 55, 26 }, { 53, 28 }, { 50, 32 }, }; // 0x51DA04 static STRUCT_51DA04 upside_down_table[13] = { { 0, 32 }, { 48, 32 }, { 49, 30 }, { 52, 26 }, { 55, 24 }, { 57, 22 }, { 60, 18 }, { 63, 16 }, { 65, 14 }, { 67, 12 }, { 70, 8 }, { 73, 6 }, { 75, 4 }, }; // 0x51DA6C static STRUCT_51DA6C verticies[10] = { { 16, -1, -201, 0 }, { 48, -2, -2, 0 }, { 960, 0, 0, 0 }, { 992, 199, -1, 0 }, { 1024, 198, 198, 0 }, { 1936, 200, 200, 0 }, { 1968, 399, 199, 0 }, { 2000, 398, 398, 0 }, { 2912, 400, 400, 0 }, { 2944, 599, 399, 0 }, }; // 0x51DB0C static STRUCT_51DB0C rightside_up_triangles[5] = { { 2, 3, 0 }, { 3, 4, 1 }, { 5, 6, 3 }, { 6, 7, 4 }, { 8, 9, 6 }, }; // 0x51DB48 static STRUCT_51DB48 upside_down_triangles[5] = { { 0, 3, 1 }, { 2, 5, 3 }, { 3, 6, 4 }, { 5, 8, 6 }, { 6, 9, 7 }, }; // 0x668224 static int intensity_map[3280]; // 0x66B564 static int dir_tile2[2][6]; // Deltas to perform tile calculations in given direction. // // 0x66B594 static int dir_tile[2][6]; // 0x66B5C4 static unsigned char tile_grid_blocked[512]; // 0x66B7C4 static unsigned char tile_grid_occupied[512]; // 0x66B9C4 static unsigned char tile_mask[512]; // 0x66BBC4 static Rect tile_border; // 0x66BBD4 static Rect buf_rect; // 0x66BBE4 static unsigned char tile_grid[32 * 16]; // 0x66BDE4 static int square_y; // 0x66BDE8 static int square_x; // 0x66BDEC static int square_offx; // 0x66BDF0 static int square_offy; // 0x66BDF4 static TileWindowRefreshProc* blit; // 0x66BDF8 static int tile_offy; // 0x66BDFC static int tile_offx; // 0x66BE00 static int square_size; // Number of tiles horizontally. // // Currently this value is always 200. // // 0x66BE04 static int grid_width; // 0x66BE08 static TileData** squares; // 0x66BE0C static unsigned char* buf; // Number of tiles vertically. // // Currently this value is always 200. // // 0x66BE10 static int grid_length; // 0x66BE14 static int buf_length; // 0x66BE18 static int tile_x; // 0x66BE1C static int tile_y; // The number of tiles in the hex grid. // // 0x66BE20 static int grid_size; // 0x66BE24 static int square_length; // 0x66BE28 static int buf_full; // 0x66BE2C static int square_width; // 0x66BE30 static int buf_width; // 0x66BE34 int tile_center_tile; // 0x4B0C40 int tile_init(TileData** a1, int squareGridWidth, int squareGridHeight, int hexGridWidth, int hexGridHeight, unsigned char* buffer, int windowWidth, int windowHeight, int windowPitch, TileWindowRefreshProc* windowRefreshProc) { int v11; int v12; int v13; int v20; int v21; int v22; int v23; int v24; int v25; square_width = squareGridWidth; squares = a1; grid_length = hexGridHeight; square_length = squareGridHeight; grid_width = hexGridWidth; dir_tile[0][0] = -1; dir_tile[0][4] = 1; dir_tile[1][1] = -1; grid_size = hexGridWidth * hexGridHeight; dir_tile[1][3] = 1; buf = buffer; dir_tile2[0][0] = -1; buf_width = windowWidth; dir_tile2[0][3] = -1; buf_length = windowHeight; dir_tile2[1][1] = 1; buf_full = windowPitch; dir_tile2[1][2] = 1; buf_rect.lrx = windowWidth - 1; square_size = squareGridHeight * squareGridWidth; buf_rect.lry = windowHeight - 1; buf_rect.ulx = 0; blit = windowRefreshProc; buf_rect.uly = 0; dir_tile[0][1] = hexGridWidth - 1; dir_tile[0][2] = hexGridWidth; show_grid = 0; dir_tile[0][3] = hexGridWidth + 1; dir_tile[1][2] = hexGridWidth; dir_tile2[0][4] = hexGridWidth; dir_tile2[0][5] = hexGridWidth; dir_tile[0][5] = -hexGridWidth; dir_tile[1][0] = -hexGridWidth - 1; dir_tile[1][4] = 1 - hexGridWidth; dir_tile[1][5] = -hexGridWidth; dir_tile2[0][1] = -hexGridWidth - 1; dir_tile2[1][4] = -hexGridWidth; dir_tile2[0][2] = hexGridWidth - 1; dir_tile2[1][5] = -hexGridWidth; dir_tile2[1][0] = hexGridWidth + 1; dir_tile2[1][3] = 1 - hexGridWidth; v11 = 0; v12 = 0; do { v13 = 64; do { tile_mask[v12++] = v13 > v11; v13 -= 4; } while (v13); do { tile_mask[v12++] = v13 > v11 ? 2 : 0; v13 += 4; } while (v13 != 64); v11 += 16; } while (v11 != 64); v11 = 0; do { v13 = 0; do { tile_mask[v12++] = 0; v13++; } while (v13 < 32); v11++; } while (v11 < 8); v11 = 0; do { v13 = 0; do { tile_mask[v12++] = v13 > v11 ? 0 : 3; v13 += 4; } while (v13 != 64); v13 = 64; do { tile_mask[v12++] = v13 > v11 ? 0 : 4; v13 -= 4; } while (v13); v11 += 16; } while (v11 != 64); buf_fill(tile_grid, 32, 16, 32, 0); draw_line(tile_grid, 32, 16, 0, 31, 4, colorTable[4228]); draw_line(tile_grid, 32, 31, 4, 31, 12, colorTable[4228]); draw_line(tile_grid, 32, 31, 12, 16, 15, colorTable[4228]); draw_line(tile_grid, 32, 0, 12, 16, 15, colorTable[4228]); draw_line(tile_grid, 32, 0, 4, 0, 12, colorTable[4228]); draw_line(tile_grid, 32, 16, 0, 0, 4, colorTable[4228]); buf_fill(tile_grid_occupied, 32, 16, 32, 0); draw_line(tile_grid_occupied, 32, 16, 0, 31, 4, colorTable[31]); draw_line(tile_grid_occupied, 32, 31, 4, 31, 12, colorTable[31]); draw_line(tile_grid_occupied, 32, 31, 12, 16, 15, colorTable[31]); draw_line(tile_grid_occupied, 32, 0, 12, 16, 15, colorTable[31]); draw_line(tile_grid_occupied, 32, 0, 4, 0, 12, colorTable[31]); draw_line(tile_grid_occupied, 32, 16, 0, 0, 4, colorTable[31]); buf_fill(tile_grid_blocked, 32, 16, 32, 0); draw_line(tile_grid_blocked, 32, 16, 0, 31, 4, colorTable[31744]); draw_line(tile_grid_blocked, 32, 31, 4, 31, 12, colorTable[31744]); draw_line(tile_grid_blocked, 32, 31, 12, 16, 15, colorTable[31744]); draw_line(tile_grid_blocked, 32, 0, 12, 16, 15, colorTable[31744]); draw_line(tile_grid_blocked, 32, 0, 4, 0, 12, colorTable[31744]); draw_line(tile_grid_blocked, 32, 16, 0, 0, 4, colorTable[31744]); for (v20 = 0; v20 < 16; v20++) { v21 = v20 * 32; v22 = 31; v23 = v21 + 31; if (tile_grid_blocked[v23] == 0) { do { --v22; --v23; } while (v22 > 0 && tile_grid_blocked[v23] == 0); } v24 = v21; v25 = 0; if (tile_grid_blocked[v21] == 0) { do { ++v25; ++v24; } while (v25 < 32 && tile_grid_blocked[v24] == 0); } draw_line(tile_grid_blocked, 32, v25, v20, v22, v20, colorTable[31744]); } tile_set_center(hexGridWidth * (hexGridHeight / 2) + hexGridWidth / 2, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS); tile_set_border(windowWidth, windowHeight, hexGridWidth, hexGridHeight); char* executable; config_get_string(&game_config, GAME_CONFIG_SYSTEM_KEY, GAME_CONFIG_EXECUTABLE_KEY, &executable); if (stricmp(executable, "mapper") == 0) { tile_refresh = refresh_mapper; } return 0; } // 0x4B11E4 void tile_set_border(int windowWidth, int windowHeight, int hexGridWidth, int hexGridHeight) { int v1 = tile_num(-320, -240, 0); int v2 = tile_num(-320, windowHeight + 240, 0); tile_border.ulx = abs(hexGridWidth - 1 - v2 % hexGridWidth - tile_x) + 6; tile_border.uly = abs(tile_y - v1 / hexGridWidth) + 7; tile_border.lrx = hexGridWidth - tile_border.ulx - 1; tile_border.lry = hexGridHeight - tile_border.uly - 1; if ((tile_border.ulx & 1) == 0) { tile_border.ulx++; } if ((tile_border.lrx & 1) == 0) { tile_border.ulx--; } borderInitialized = true; } // NOTE: Uncollapsed 0x4B129C. void tile_reset() { } // NOTE: Uncollapsed 0x4B129C. void tile_exit() { } // 0x4B12A8 void tile_disable_refresh() { refresh_enabled = false; } // 0x4B12B4 void tile_enable_refresh() { refresh_enabled = true; } // 0x4B12C0 void tile_refresh_rect(Rect* rect, int elevation) { if (refresh_enabled) { if (elevation == map_elevation) { tile_refresh(rect, elevation); } } } // 0x4B12D8 void tile_refresh_display() { if (refresh_enabled) { tile_refresh(&buf_rect, map_elevation); } } // 0x4B12F8 int tile_set_center(int tile, int flags) { if (!TILE_IS_VALID(tile)) { return -1; } if ((flags & TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) == 0) { if (scroll_limiting_on) { int tileScreenX; int tileScreenY; tile_coord(tile, &tileScreenX, &tileScreenY, map_elevation); int dudeScreenX; int dudeScreenY; tile_coord(obj_dude->tile, &dudeScreenX, &dudeScreenY, map_elevation); int dx = abs(dudeScreenX - tileScreenX); int dy = abs(dudeScreenY - tileScreenY); if (dx > abs(dudeScreenX - tile_offx) || dy > abs(dudeScreenY - tile_offy)) { if (dx >= 480 || dy >= 400) { return -1; } } } if (scroll_blocking_on) { if (obj_scroll_blocking_at(tile, map_elevation) == 0) { return -1; } } } int tile_x = grid_width - 1 - tile % grid_width; int tile_y = tile / grid_width; if (borderInitialized) { if (tile_x <= tile_border.ulx || tile_x >= tile_border.lrx || tile_y <= tile_border.uly || tile_y >= tile_border.lry) { return -1; } } tile_y = tile_y; tile_offx = (buf_width - 32) / 2; tile_x = tile_x; tile_offy = (buf_length - 16) / 2; if (tile_x & 1) { tile_x -= 1; tile_offx -= 32; } square_x = tile_x / 2; square_y = tile_y / 2; square_offx = tile_offx - 16; square_offy = tile_offy - 2; if (tile_y & 1) { square_offy -= 12; square_offx -= 16; } tile_center_tile = tile; if ((flags & TILE_SET_CENTER_REFRESH_WINDOW) != 0) { // NOTE: Uninline. tile_refresh_display(); } return 0; } // 0x4B1554 static void refresh_mapper(Rect* rect, int elevation) { Rect rectToUpdate; if (rect_inside_bound(rect, &buf_rect, &rectToUpdate) == -1) { return; } buf_fill(buf + buf_full * rectToUpdate.uly + rectToUpdate.ulx, rectToUpdate.lrx - rectToUpdate.ulx + 1, rectToUpdate.lry - rectToUpdate.uly + 1, buf_full, 0); square_render_floor(&rectToUpdate, elevation); grid_render(&rectToUpdate, elevation); obj_render_pre_roof(&rectToUpdate, elevation); square_render_roof(&rectToUpdate, elevation); obj_render_post_roof(&rectToUpdate, elevation); blit(&rectToUpdate); } // 0x4B15E8 static void refresh_game(Rect* rect, int elevation) { Rect rectToUpdate; if (rect_inside_bound(rect, &buf_rect, &rectToUpdate) == -1) { return; } square_render_floor(&rectToUpdate, elevation); obj_render_pre_roof(&rectToUpdate, elevation); square_render_roof(&rectToUpdate, elevation); obj_render_post_roof(&rectToUpdate, elevation); blit(&rectToUpdate); } // 0x4B1634 void tile_toggle_roof(int a1) { show_roof = 1 - show_roof; if (a1) { // NOTE: Uninline. tile_refresh_display(); } } // 0x4B166C int tile_roof_visible() { return show_roof; } // 0x4B1674 int tile_coord(int tile, int* screenX, int* screenY, int elevation) { int v3; int v4; int v5; int v6; if (!TILE_IS_VALID(tile)) { return -1; } v3 = grid_width - 1 - tile % grid_width; v4 = tile / grid_width; *screenX = tile_offx; *screenY = tile_offy; v5 = (v3 - tile_x) / -2; *screenX += 48 * ((v3 - tile_x) / 2); *screenY += 12 * v5; if (v3 & 1) { if (v3 <= tile_x) { *screenX -= 16; *screenY += 12; } else { *screenX += 32; } } v6 = v4 - tile_y; *screenX += 16 * v6; *screenY += 12 * v6; return 0; } // 0x4B1754 int tile_num(int screenX, int screenY, int elevation) { int v2; int v3; int v4; int v5; int v6; int v7; int v8; int v9; int v10; int v11; int v12; v2 = screenY - tile_offy; if (v2 >= 0) { v3 = v2 / 12; } else { v3 = (v2 + 1) / 12 - 1; } v4 = screenX - tile_offx - 16 * v3; v5 = v2 - 12 * v3; if (v4 >= 0) { v6 = v4 / 64; } else { v6 = (v4 + 1) / 64 - 1; } v7 = v6 + v3; v8 = v4 - (v6 * 64); v9 = 2 * v6; if (v8 >= 32) { v8 -= 32; v9++; } v10 = tile_y + v7; v11 = tile_x + v9; switch (tile_mask[32 * v5 + v8]) { case 2: v11++; if (v11 & 1) { v10--; } break; case 1: v10--; break; case 3: v11--; if (!(v11 & 1)) { v10++; } break; case 4: v10++; break; default: break; } v12 = grid_width - 1 - v11; if (v12 >= 0 && v12 < grid_width && v10 >= 0 && v10 < grid_length) { return grid_width * v10 + v12; } return -1; } // tile_distance // 0x4B185C int tile_dist(int tile1, int tile2) { int i; int v9; int v8; int v2; if (tile1 == -1) { return 9999; } if (tile2 == -1) { return 9999; } int x1; int y1; tile_coord(tile2, &x1, &y1, 0); v2 = tile1; for (i = 0; v2 != tile2; i++) { // TODO: Looks like inlined rotation_to_tile. int x2; int y2; tile_coord(v2, &x2, &y2, 0); int dx = x1 - x2; int dy = y1 - y2; if (x1 == x2) { if (dy < 0) { v9 = 0; } else { v9 = 2; } } else { v8 = (int)trunc(atan2((double)-dy, (double)dx) * 180.0 * 0.3183098862851122); v9 = 360 - (v8 + 180) - 90; if (v9 < 0) { v9 += 360; } v9 /= 60; if (v9 >= 6) { v9 = 5; } } v2 += dir_tile[v2 % grid_width & 1][v9]; } return i; } // 0x4B1994 bool tile_in_front_of(int tile1, int tile2) { int x1; int y1; tile_coord(tile1, &x1, &y1, 0); int x2; int y2; tile_coord(tile2, &x2, &y2, 0); int dx = x2 - x1; int dy = y2 - y1; return (double)dx <= (double)dy * -4.0; } // 0x4B1A00 bool tile_to_right_of(int tile1, int tile2) { int x1; int y1; tile_coord(tile1, &x1, &y1, 0); int x2; int y2; tile_coord(tile2, &x2, &y2, 0); int dx = x2 - x1; int dy = y2 - y1; // NOTE: the value below looks like 4/3, which is 0x3FF55555555555, but it's // binary value is slightly different: 0x3FF55555555556. This difference plays // important role as seen right in the beginning of the game, comparing tiles // 17488 (0x4450) and 15288 (0x3BB8). return (double)dx <= (double)dy * 1.3333333333333335; } // tile_num_in_direction // 0x4B1A6C int tile_num_in_direction(int tile, int rotation, int distance) { int newTile = tile; for (int index = 0; index < distance; index++) { if (tile_on_edge(newTile)) { break; } int parity = (newTile % grid_width) & 1; newTile += dir_tile[parity][rotation]; } return newTile; } // rotation_to_tile // 0x4B1ABC int tile_dir(int tile1, int tile2) { int x1; int y1; tile_coord(tile1, &x1, &y1, 0); int x2; int y2; tile_coord(tile2, &x2, &y2, 0); int dy = y2 - y1; x2 -= x1; y2 -= y1; if (x2 != 0) { // TODO: Check. int v6 = (int)trunc(atan2((double)-dy, (double)x2) * 180.0 * 0.3183098862851122); int v7 = 360 - (v6 + 180) - 90; if (v7 < 0) { v7 += 360; } v7 /= 60; if (v7 >= ROTATION_COUNT) { v7 = ROTATION_NW; } return v7; } return dy < 0 ? ROTATION_NE : ROTATION_SE; } // 0x4B1B84 int tile_num_beyond(int from, int to, int distance) { if (distance <= 0 || from == to) { return from; } int fromX; int fromY; tile_coord(from, &fromX, &fromY, 0); fromX += 16; fromY += 8; int toX; int toY; tile_coord(to, &toX, &toY, 0); toX += 16; toY += 8; int deltaX = toX - fromX; int deltaY = toY - fromY; int v27 = 2 * abs(deltaX); int stepX = 0; if (deltaX > 0) stepX = 1; else if (deltaX < 0) stepX = -1; int v26 = 2 * abs(deltaY); int stepY = 0; if (deltaY > 0) stepY = 1; else if (deltaY < 0) stepY = -1; int v28 = from; int tileX = fromX; int tileY = fromY; int v6 = 0; if (v27 > v26) { int middle = v26 - v27 / 2; while (true) { int tile = tile_num(tileX, tileY, 0); if (tile != v28) { v6 += 1; if (v6 == distance || tile_on_edge(tile)) { return tile; } v28 = tile; } if (middle >= 0) { middle -= v27; tileY += stepY; } middle += v26; tileX += stepX; } } else { int middle = v27 - v26 / 2; while (true) { int tile = tile_num(tileX, tileY, 0); if (tile != v28) { v6 += 1; if (v6 == distance || tile_on_edge(tile)) { return tile; } v28 = tile; } if (middle >= 0) { middle -= v26; tileX += stepX; } middle += v27; tileY += stepY; } } assert(false && "Should be unreachable"); } // 0x4B1D20 static bool tile_on_edge(int tile) { if (!TILE_IS_VALID(tile)) { return false; } if (tile < grid_width) { return true; } if (tile >= grid_size - grid_width) { return true; } if (tile % grid_width == 0) { return true; } if (tile % grid_width == grid_width - 1) { return true; } return false; } // 0x4B1D80 void tile_enable_scroll_blocking() { scroll_blocking_on = true; } // 0x4B1D8C void tile_disable_scroll_blocking() { scroll_blocking_on = false; } // 0x4B1D98 bool tile_get_scroll_blocking() { return scroll_blocking_on; } // 0x4B1DA0 void tile_enable_scroll_limiting() { scroll_limiting_on = true; } // 0x4B1DAC void tile_disable_scroll_limiting() { scroll_limiting_on = false; } // 0x4B1DB8 bool tile_get_scroll_limiting() { return scroll_limiting_on; } // 0x4B1DC0 int square_coord(int squareTile, int* coordX, int* coordY, int elevation) { int v5; int v6; int v7; int v8; int v9; if (squareTile < 0 || squareTile >= square_size) { return -1; } v5 = square_width - 1 - squareTile % square_width; v6 = squareTile / square_width; v7 = square_x; *coordX = square_offx; *coordY = square_offy; v8 = v5 - v7; *coordX += 48 * v8; *coordY -= 12 * v8; v9 = v6 - square_y; *coordX += 32 * v9; *coordY += 24 * v9; return 0; } // 0x4B1E60 int square_coord_roof(int squareTile, int* screenX, int* screenY, int elevation) { int v5; int v6; int v7; int v8; int v9; int v10; if (squareTile < 0 || squareTile >= square_size) { return -1; } v5 = square_width - 1 - squareTile % square_width; v6 = squareTile / square_width; v7 = square_x; *screenX = square_offx; *screenY = square_offy; v8 = v5 - v7; *screenX += 48 * v8; *screenY -= 12 * v8; v9 = v6 - square_y; *screenX += 32 * v9; v10 = 24 * v9 + *screenY; *screenY = v10; *screenY = v10 - 96; return 0; } // 0x4B1F04 int square_num(int screenX, int screenY, int elevation) { int coordY; int coordX; square_xy(screenX, screenY, elevation, &coordX, &coordY); if (coordX >= 0 && coordX < square_width && coordY >= 0 && coordY < square_length) { return coordX + square_width * coordY; } return -1; } // NOTE: Unused. // // 0x4B1F4C int square_num_roof(int screenX, int screenY, int elevation) { int x; int y; square_xy_roof(screenX, screenY, elevation, &x, &y); if (x >= 0 && x < square_width && y >= 0 && y < square_length) { return x + square_width * y; } return -1; } // 0x4B1F94 void square_xy(int screenX, int screenY, int elevation, int* coordX, int* coordY) { int v4; int v5; int v6; int v8; v4 = screenX - square_offx; v5 = screenY - square_offy - 12; v6 = 3 * v4 - 4 * v5; *coordX = v6 >= 0 ? (v6 / 192) : ((v6 + 1) / 192 - 1); v8 = 4 * v5 + v4; *coordY = v8 >= 0 ? (v8 / 128) : ((v8 + 1) / 128 - 1); *coordX += square_x; *coordY += square_y; *coordX = square_width - 1 - *coordX; } // 0x4B203C void square_xy_roof(int screenX, int screenY, int elevation, int* coordX, int* coordY) { int v4; int v5; int v6; int v8; v4 = screenX - square_offx; v5 = screenY + 96 - square_offy - 12; v6 = 3 * v4 - 4 * v5; *coordX = (v6 >= 0) ? (v6 / 192) : ((v6 + 1) / 192 - 1); v8 = 4 * v5 + v4; *coordY = v8 >= 0 ? (v8 / 128) : ((v8 + 1) / 128 - 1); *coordX += square_x; *coordY += square_y; *coordX = square_width - 1 - *coordX; } // 0x4B20E8 void square_render_roof(Rect* rect, int elevation) { if (!show_roof) { return; } int temp; int minY; int minX; int maxX; int maxY; square_xy_roof(rect->ulx, rect->uly, elevation, &temp, &minY); square_xy_roof(rect->lrx, rect->uly, elevation, &minX, &temp); square_xy_roof(rect->ulx, rect->lry, elevation, &maxX, &temp); square_xy_roof(rect->lrx, rect->lry, elevation, &temp, &maxY); if (minX < 0) { minX = 0; } if (minX >= square_width) { minX = square_width - 1; } if (minY < 0) { minY = 0; } // FIXME: Probably a bug - testing X, then changing Y. if (minX >= square_length) { minY = square_length - 1; } int light = light_get_ambient(); int baseSquareTile = square_width * minY; for (int y = minY; y <= maxY; y++) { for (int x = minX; x <= maxX; x++) { int squareTile = baseSquareTile + x; int frmId = squares[elevation]->field_0[squareTile]; frmId >>= 16; if ((((frmId & 0xF000) >> 12) & 0x01) == 0) { int fid = art_id(OBJ_TYPE_TILE, frmId & 0xFFF, 0, 0, 0); if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { int screenX; int screenY; square_coord_roof(squareTile, &screenX, &screenY, elevation); roof_draw(fid, screenX, screenY, rect, light); } } } baseSquareTile += square_width; } } // 0x4B22D0 static void roof_fill_on(int a1, int a2, int elevation) { while ((a1 >= 0 && a1 < square_width) && (a2 >= 0 && a2 < square_length)) { int squareTile = square_width * a2 + a1; int value = squares[elevation]->field_0[squareTile]; int upper = (value >> 16) & 0xFFFF; int id = upper & 0xFFF; if (art_id(OBJ_TYPE_TILE, id, 0, 0, 0) == art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { break; } int flag = (upper & 0xF000) >> 12; if ((flag & 0x01) == 0) { break; } flag &= ~0x01; squares[elevation]->field_0[squareTile] = (value & 0xFFFF) | (((flag << 12) | id) << 16); roof_fill_on(a1 - 1, a2, elevation); roof_fill_on(a1 + 1, a2, elevation); roof_fill_on(a1, a2 - 1, elevation); a2++; } } // 0x4B23D4 void tile_fill_roof(int a1, int a2, int elevation, int a4) { if (a4) { roof_fill_on(a1, a2, elevation); } else { roof_fill_off(a1, a2, elevation); } } // 0x4B23DC static void roof_fill_off(int a1, int a2, int elevation) { while ((a1 >= 0 && a1 < square_width) && (a2 >= 0 && a2 < square_length)) { int squareTile = square_width * a2 + a1; int value = squares[elevation]->field_0[squareTile]; int upper = (value >> 16) & 0xFFFF; int id = upper & 0xFFF; if (art_id(OBJ_TYPE_TILE, id, 0, 0, 0) == art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { break; } int flag = (upper & 0xF000) >> 12; if ((flag & 0x03) != 0) { break; } flag |= 0x01; squares[elevation]->field_0[squareTile] = (value & 0xFFFF) | (((flag << 12) | id) << 16); roof_fill_off(a1 - 1, a2, elevation); roof_fill_off(a1 + 1, a2, elevation); roof_fill_off(a1, a2 - 1, elevation); a2++; } } // 0x4B24E0 static void roof_draw(int fid, int x, int y, Rect* rect, int light) { CacheEntry* tileFrmHandle; Art* tileFrm = art_ptr_lock(fid, &tileFrmHandle); if (tileFrm == NULL) { return; } int tileWidth = art_frame_width(tileFrm, 0, 0); int tileHeight = art_frame_length(tileFrm, 0, 0); Rect tileRect; tileRect.ulx = x; tileRect.uly = y; tileRect.lrx = x + tileWidth - 1; tileRect.lry = y + tileHeight - 1; if (rect_inside_bound(&tileRect, rect, &tileRect) == 0) { unsigned char* tileFrmBuffer = art_frame_data(tileFrm, 0, 0); tileFrmBuffer += tileWidth * (tileRect.uly - y) + (tileRect.ulx - x); CacheEntry* eggFrmHandle; Art* eggFrm = art_ptr_lock(obj_egg->fid, &eggFrmHandle); if (eggFrm != NULL) { int eggWidth = art_frame_width(eggFrm, 0, 0); int eggHeight = art_frame_length(eggFrm, 0, 0); int eggScreenX; int eggScreenY; tile_coord(obj_egg->tile, &eggScreenX, &eggScreenY, obj_egg->elevation); eggScreenX += 16; eggScreenY += 8; eggScreenX += eggFrm->xOffsets[0]; eggScreenY += eggFrm->yOffsets[0]; eggScreenX += obj_egg->x; eggScreenY += obj_egg->y; Rect eggRect; eggRect.ulx = eggScreenX - eggWidth / 2; eggRect.uly = eggScreenY - eggHeight + 1; eggRect.lrx = eggRect.ulx + eggWidth - 1; eggRect.lry = eggScreenY; obj_egg->sx = eggRect.ulx; obj_egg->sy = eggRect.uly; Rect intersectedRect; if (rect_inside_bound(&eggRect, &tileRect, &intersectedRect) == 0) { Rect rects[4]; rects[0].ulx = tileRect.ulx; rects[0].uly = tileRect.uly; rects[0].lrx = tileRect.lrx; rects[0].lry = intersectedRect.uly - 1; rects[1].ulx = tileRect.ulx; rects[1].uly = intersectedRect.uly; rects[1].lrx = intersectedRect.ulx - 1; rects[1].lry = intersectedRect.lry; rects[2].ulx = intersectedRect.lrx + 1; rects[2].uly = intersectedRect.uly; rects[2].lrx = tileRect.lrx; rects[2].lry = intersectedRect.lry; rects[3].ulx = tileRect.ulx; rects[3].uly = intersectedRect.lry + 1; rects[3].lrx = tileRect.lrx; rects[3].lry = tileRect.lry; for (int i = 0; i < 4; i++) { Rect* cr = &(rects[i]); if (cr->ulx <= cr->lrx && cr->uly <= cr->lry) { dark_trans_buf_to_buf(tileFrmBuffer + tileWidth * (cr->uly - tileRect.uly) + (cr->ulx - tileRect.ulx), cr->lrx - cr->ulx + 1, cr->lry - cr->uly + 1, tileWidth, buf, cr->ulx, cr->uly, buf_full, light); } } unsigned char* eggBuf = art_frame_data(eggFrm, 0, 0); intensity_mask_buf_to_buf(tileFrmBuffer + tileWidth * (intersectedRect.uly - tileRect.uly) + (intersectedRect.ulx - tileRect.ulx), intersectedRect.lrx - intersectedRect.ulx + 1, intersectedRect.lry - intersectedRect.uly + 1, tileWidth, buf + buf_full * intersectedRect.uly + intersectedRect.ulx, buf_full, eggBuf + eggWidth * (intersectedRect.uly - eggRect.uly) + (intersectedRect.ulx - eggRect.ulx), eggWidth, light); } else { dark_trans_buf_to_buf(tileFrmBuffer, tileRect.lrx - tileRect.ulx + 1, tileRect.lry - tileRect.uly + 1, tileWidth, buf, tileRect.ulx, tileRect.uly, buf_full, light); } art_ptr_unlock(eggFrmHandle); } } art_ptr_unlock(tileFrmHandle); } // 0x4B2944 void square_render_floor(Rect* rect, int elevation) { int minY; int maxX; int maxY; int minX; int temp; square_xy(rect->ulx, rect->uly, elevation, &temp, &minY); square_xy(rect->lrx, rect->uly, elevation, &minX, &temp); square_xy(rect->ulx, rect->lry, elevation, &maxX, &temp); square_xy(rect->lrx, rect->lry, elevation, &temp, &maxY); if (minX < 0) { minX = 0; } if (minX >= square_width) { minX = square_width - 1; } if (minY < 0) { minY = 0; } if (minX >= square_length) { minY = square_length - 1; } light_get_ambient(); temp = square_width * minY; for (int v15 = minY; v15 <= maxY; v15++) { for (int i = minX; i <= maxX; i++) { int v3 = temp + i; int frmId = squares[elevation]->field_0[v3]; if ((((frmId & 0xF000) >> 12) & 0x01) == 0) { int v12; int v13; square_coord(v3, &v12, &v13, elevation); int fid = art_id(OBJ_TYPE_TILE, frmId & 0xFFF, 0, 0, 0); floor_draw(fid, v12, v13, rect); } } temp += square_width; } } // 0x4B2B10 bool square_roof_intersect(int x, int y, int elevation) { if (!show_roof) { return false; } bool result = false; int tileX; int tileY; square_xy_roof(x, y, elevation, &tileX, &tileY); TileData* ptr = squares[elevation]; int idx = square_width * tileY + tileX; int upper = ptr->field_0[square_width * tileY + tileX] >> 16; int fid = art_id(OBJ_TYPE_TILE, upper & 0xFFF, 0, 0, 0); if (fid != art_id(OBJ_TYPE_TILE, 1, 0, 0, 0)) { if ((((upper & 0xF000) >> 12) & 1) == 0) { int fid = art_id(OBJ_TYPE_TILE, upper & 0xFFF, 0, 0, 0); CacheEntry* handle; Art* art = art_ptr_lock(fid, &handle); if (art != NULL) { unsigned char* data = art_frame_data(art, 0, 0); if (data != NULL) { int v18; int v17; square_coord_roof(idx, &v18, &v17, elevation); int width = art_frame_width(art, 0, 0); if (data[width * (y - v17) + x - v18] != 0) { result = true; } } art_ptr_unlock(handle); } } } return result; } // NOTE: Unused. // // 0x4B2E60 void grid_toggle() { show_grid = 1 - show_grid; } // NOTE: Unused. // // 0x4B2E78 void grid_on() { show_grid = 1; } // NOTE: Unused. // // 0x4B2E84 void grid_off() { show_grid = 0; } // 0x4B2E90 int get_grid_flag() { return show_grid; } // 0x4B2E98 void grid_render(Rect* rect, int elevation) { if (!show_grid) { return; } for (int y = rect->uly - 12; y < rect->lry + 12; y += 6) { for (int x = rect->ulx - 32; x < rect->lrx + 32; x += 16) { int tile = tile_num(x, y, elevation); draw_grid(tile, elevation, rect); } } } // 0x4B2EF0 void grid_draw(int tile, int elevation) { Rect rect; tile_coord(tile, &(rect.ulx), &(rect.uly), elevation); rect.lrx = rect.ulx + 32 - 1; rect.lry = rect.uly + 16 - 1; if (rect_inside_bound(&rect, &buf_rect, &rect) != -1) { draw_grid(tile, elevation, &rect); blit(&rect); } } // 0x4B2F4C void draw_grid(int tile, int elevation, Rect* rect) { if (tile == -1) { return; } int x; int y; tile_coord(tile, &x, &y, elevation); Rect r; r.ulx = x; r.uly = y; r.lrx = x + 32 - 1; r.lry = y + 16 - 1; if (rect_inside_bound(&r, rect, &r) == -1) { return; } if (obj_blocking_at(NULL, tile, elevation) != NULL) { trans_buf_to_buf(tile_grid_blocked + 32 * (r.uly - y) + (r.ulx - x), r.lrx - r.ulx + 1, r.lry - r.uly + 1, 32, buf + buf_full * r.uly + r.ulx, buf_full); return; } if (obj_occupied(tile, elevation)) { trans_buf_to_buf(tile_grid_occupied + 32 * (r.uly - y) + (r.ulx - x), r.lrx - r.ulx + 1, r.lry - r.uly + 1, 32, buf + buf_full * r.uly + r.ulx, buf_full); return; } translucent_trans_buf_to_buf(tile_grid_occupied + 32 * (r.uly - y) + (r.ulx - x), r.lrx - r.ulx + 1, r.lry - r.uly + 1, 32, buf + buf_full * r.uly + r.ulx, 0, 0, buf_full, wallBlendTable, commonGrayTable); } // 0x4B30C4 void floor_draw(int fid, int x, int y, Rect* rect) { if (art_get_disable(FID_TYPE(fid)) != 0) { return; } CacheEntry* cacheEntry; Art* art = art_ptr_lock(fid, &cacheEntry); if (art == NULL) { return; } int elev = map_elevation; int left = rect->ulx; int top = rect->uly; int width = rect->lrx - rect->ulx + 1; int height = rect->lry - rect->uly + 1; int frameWidth; int frameHeight; int v15; int v76; int v77; int v78; int v79; int savedX = x; int savedY = y; if (left < 0) { left = 0; } if (top < 0) { top = 0; } if (left + width > buf_width) { width = buf_width - left; } if (top + height > buf_length) { height = buf_length - top; } if (x >= buf_width || x > rect->lrx || y >= buf_length || y > rect->lry) goto out; frameWidth = art_frame_width(art, 0, 0); frameHeight = art_frame_length(art, 0, 0); if (left < x) { v79 = 0; int v12 = left + width; v77 = frameWidth + x <= v12 ? frameWidth : v12 - x; } else { v79 = left - x; x = left; v77 = frameWidth - v79; if (v77 > width) { v77 = width; } } if (top < y) { int v14 = height + top; v78 = 0; v76 = frameHeight + y <= v14 ? frameHeight : v14 - y; } else { v78 = top - y; y = top; v76 = frameHeight - v78; if (v76 > height) { v76 = height; } } if (v77 <= 0 || v76 <= 0) goto out; v15 = tile_num(savedX, savedY + 13, map_elevation); if (v15 != -1) { int v17 = light_get_ambient(); for (int i = v15 & 1; i < 10; i++) { // NOTE: calling light_get_tile two times, probably a result of using __min kind macro int v21 = light_get_tile(elev, v15 + verticies[i].field_4); if (v21 <= v17) { v21 = v17; } verticies[i].field_C = v21; } int v23 = 0; for (int i = 0; i < 9; i++) { if (verticies[i + 1].field_C != verticies[i].field_C) { break; } v23++; } if (v23 == 9) { unsigned char* frame_data = art_frame_data(art, 0, 0); dark_trans_buf_to_buf(frame_data + frameWidth * v78 + v79, v77, v76, frameWidth, buf, x, y, buf_full, verticies[0].field_C); goto out; } for (int i = 0; i < 5; i++) { STRUCT_51DB0C* ptr_51DB0C = &(rightside_up_triangles[i]); int v32 = verticies[ptr_51DB0C->field_8].field_C; int v33 = verticies[ptr_51DB0C->field_8].field_0; int v34 = verticies[ptr_51DB0C->field_4].field_C - verticies[ptr_51DB0C->field_0].field_C; // TODO: Probably wrong. int v35 = v34 / 32; int v36 = (verticies[ptr_51DB0C->field_0].field_C - v32) / 13; int* v37 = &(intensity_map[v33]); if (v35 != 0) { if (v36 != 0) { for (int i = 0; i < 13; i++) { int v41 = v32; int v42 = rightside_up_table[i].field_4; v37 += rightside_up_table[i].field_0; for (int j = 0; j < v42; j++) { *v37++ = v41; v41 += v35; } v32 += v36; } } else { for (int i = 0; i < 13; i++) { int v38 = v32; int v39 = rightside_up_table[i].field_4; v37 += rightside_up_table[i].field_0; for (int j = 0; j < v39; j++) { *v37++ = v38; v38 += v35; } } } } else { if (v36 != 0) { for (int i = 0; i < 13; i++) { int v46 = rightside_up_table[i].field_4; v37 += rightside_up_table[i].field_0; for (int j = 0; j < v46; j++) { *v37++ = v32; } v32 += v36; } } else { for (int i = 0; i < 13; i++) { int v44 = rightside_up_table[i].field_4; v37 += rightside_up_table[i].field_0; for (int j = 0; j < v44; j++) { *v37++ = v32; } } } } } for (int i = 0; i < 5; i++) { STRUCT_51DB48* ptr_51DB48 = &(upside_down_triangles[i]); int v50 = verticies[ptr_51DB48->field_0].field_C; int v51 = verticies[ptr_51DB48->field_0].field_0; int v52 = verticies[ptr_51DB48->field_8].field_C - v50; // TODO: Probably wrong. int v53 = v52 / 32; int v54 = (verticies[ptr_51DB48->field_4].field_C - v50) / 13; int* v55 = &(intensity_map[v51]); if (v53 != 0) { if (v54 != 0) { for (int i = 0; i < 13; i++) { int v59 = v50; int v60 = upside_down_table[i].field_4; v55 += upside_down_table[i].field_0; for (int j = 0; j < v60; j++) { *v55++ = v59; v59 += v53; } v50 += v54; } } else { for (int i = 0; i < 13; i++) { int v56 = v50; int v57 = upside_down_table[i].field_4; v55 += upside_down_table[i].field_0; for (int j = 0; j < v57; j++) { *v55++ = v56; v56 += v53; } } } } else { if (v54 != 0) { for (int i = 0; i < 13; i++) { int v64 = upside_down_table[i].field_4; v55 += upside_down_table[i].field_0; for (int j = 0; j < v64; j++) { *v55++ = v50; } v50 += v54; } } else { for (int i = 0; i < 13; i++) { int v62 = upside_down_table[i].field_4; v55 += upside_down_table[i].field_0; for (int j = 0; j < v62; j++) { *v55++ = v50; } } } } } unsigned char* v66 = buf + buf_full * y + x; unsigned char* v67 = art_frame_data(art, 0, 0) + frameWidth * v78 + v79; int* v68 = &(intensity_map[160 + 80 * v78]) + v79; int v86 = frameWidth - v77; int v85 = buf_full - v77; int v87 = 80 - v77; while (--v76 != -1) { for (int kk = 0; kk < v77; kk++) { if (*v67 != 0) { *v66 = intensityColorTable[*v67][*v68 >> 9]; } v67++; v68++; v66++; } v66 += v85; v68 += v87; v67 += v86; } } out: art_ptr_unlock(cacheEntry); } // 0x4B372C int tile_make_line(int from, int to, int* tiles, int tilesCapacity) { if (tilesCapacity <= 1) { return 0; } int count = 0; int fromX; int fromY; tile_coord(from, &fromX, &fromY, map_elevation); fromX += 16; fromY += 8; int toX; int toY; tile_coord(to, &toX, &toY, map_elevation); toX += 16; toY += 8; tiles[count++] = from; int stepX; int deltaX = toX - fromX; if (deltaX > 0) stepX = 1; else if (deltaX < 0) stepX = -1; else stepX = 0; int stepY; int deltaY = toY - fromY; if (deltaY > 0) stepY = 1; else if (deltaY < 0) stepY = -1; else stepY = 0; int v28 = 2 * abs(toX - fromX); int v27 = 2 * abs(toY - fromY); int tileX = fromX; int tileY = fromY; if (v28 <= v27) { int middleX = v28 - v27 / 2; while (true) { int tile = tile_num(tileX, tileY, map_elevation); tiles[count] = tile; if (tile == to) { count++; break; } if (tile != tiles[count - 1] && (count == 1 || tile != tiles[count - 2])) { count++; if (count == tilesCapacity) { break; } } if (tileY == toY) { break; } if (middleX >= 0) { tileX += stepX; middleX -= v27; } middleX += v28; tileY += stepY; } } else { int middleY = v27 - v28 / 2; while (true) { int tile = tile_num(tileX, tileY, map_elevation); tiles[count] = tile; if (tile == to) { count++; break; } if (tile != tiles[count - 1] && (count == 1 || tile != tiles[count - 2])) { count++; if (count == tilesCapacity) { break; } } if (tileX == toX) { return count; } if (middleY >= 0) { tileY += stepY; middleY -= v28; } middleY += v27; tileX += stepX; } } return count; } // 0x4B3924 int tile_scroll_to(int tile, int flags) { if (tile == tile_center_tile) { return -1; } int oldCenterTile = tile_center_tile; int v9[200]; int count = tile_make_line(tile_center_tile, tile, v9, 200); if (count == 0) { return -1; } int index = 1; for (; index < count; index++) { if (tile_set_center(v9[index], 0) == -1) { break; } } int rc = 0; if ((flags & 0x01) != 0) { if (index != count) { tile_set_center(oldCenterTile, 0); rc = -1; } } if ((flags & 0x02) != 0) { // NOTE: Uninline. tile_refresh_display(); } return rc; } ================================================ FILE: src/game/tile.h ================================================ #ifndef FALLOUT_GAME_TILE_H_ #define FALLOUT_GAME_TILE_H_ #include #include "plib/gnw/rect.h" #include "game/map.h" #include "game/object_types.h" #define TILE_SET_CENTER_REFRESH_WINDOW 0x01 #define TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS 0x02 typedef void(TileWindowRefreshProc)(Rect* rect); typedef void(TileWindowRefreshElevationProc)(Rect* rect, int elevation); extern int off_tile[2][6]; extern int tile_center_tile; int tile_init(TileData** a1, int squareGridWidth, int squareGridHeight, int hexGridWidth, int hexGridHeight, unsigned char* buf, int windowWidth, int windowHeight, int windowPitch, TileWindowRefreshProc* windowRefreshProc); void tile_set_border(int windowWidth, int windowHeight, int hexGridWidth, int hexGridHeight); void tile_reset(); void tile_exit(); void tile_disable_refresh(); void tile_enable_refresh(); void tile_refresh_rect(Rect* rect, int elevation); void tile_refresh_display(); int tile_set_center(int tile, int flags); void tile_toggle_roof(int a1); int tile_roof_visible(); int tile_coord(int tile, int* x, int* y, int elevation); int tile_num(int x, int y, int elevation); int tile_dist(int a1, int a2); bool tile_in_front_of(int tile1, int tile2); bool tile_to_right_of(int tile1, int tile2); int tile_num_in_direction(int tile, int rotation, int distance); int tile_dir(int a1, int a2); int tile_num_beyond(int from, int to, int distance); void tile_enable_scroll_blocking(); void tile_disable_scroll_blocking(); bool tile_get_scroll_blocking(); void tile_enable_scroll_limiting(); void tile_disable_scroll_limiting(); bool tile_get_scroll_limiting(); int square_coord(int squareTile, int* coordX, int* coordY, int elevation); int square_coord_roof(int squareTile, int* screenX, int* screenY, int elevation); int square_num(int screenX, int screenY, int elevation); int square_num_roof(int screenX, int screenY, int elevation); void square_xy(int screenX, int screenY, int elevation, int* coordX, int* coordY); void square_xy_roof(int screenX, int screenY, int elevation, int* coordX, int* coordY); void square_render_roof(Rect* rect, int elevation); void tile_fill_roof(int x, int y, int elevation, int a4); void square_render_floor(Rect* rect, int elevation); bool square_roof_intersect(int x, int y, int elevation); void grid_toggle(); void grid_on(); void grid_off(); int get_grid_flag(); void grid_render(Rect* rect, int elevation); void grid_draw(int tile, int elevation); void draw_grid(int tile, int elevation, Rect* rect); void floor_draw(int fid, int x, int y, Rect* rect); int tile_make_line(int currentCenterTile, int newCenterTile, int* tiles, int tilesCapacity); int tile_scroll_to(int tile, int flags); #endif /* FALLOUT_GAME_TILE_H_ */ ================================================ FILE: src/game/trait.c ================================================ #include "game/trait.h" #include #include "game/game.h" #include "game/message.h" #include "game/object.h" #include "game/skill.h" #include "game/stat.h" // Provides metadata about traits. typedef struct TraitDescription { // The name of trait. char* name; // The description of trait. // // The description is only used in character editor to inform player about // effects of this trait. char* description; // Identifier of art in [intrface.lst]. int frmId; } TraitDescription; // 0x66BE38 static MessageList trait_message_file; // List of selected traits. // // 0x66BE40 static int pc_trait[TRAITS_MAX_SELECTED_COUNT]; // 0x51DB84 static TraitDescription trait_data[TRAIT_COUNT] = { { NULL, NULL, 55 }, { NULL, NULL, 56 }, { NULL, NULL, 57 }, { NULL, NULL, 58 }, { NULL, NULL, 59 }, { NULL, NULL, 60 }, { NULL, NULL, 61 }, { NULL, NULL, 62 }, { NULL, NULL, 63 }, { NULL, NULL, 64 }, { NULL, NULL, 65 }, { NULL, NULL, 66 }, { NULL, NULL, 67 }, { NULL, NULL, 94 }, { NULL, NULL, 69 }, { NULL, NULL, 70 }, }; // 0x4B39F0 int trait_init() { if (!message_init(&trait_message_file)) { return -1; } char path[FILENAME_MAX]; sprintf(path, "%s%s", msg_path, "trait.msg"); if (!message_load(&trait_message_file, path)) { return -1; } for (int trait = 0; trait < TRAIT_COUNT; trait++) { MessageListItem messageListItem; messageListItem.num = 100 + trait; if (message_search(&trait_message_file, &messageListItem)) { trait_data[trait].name = messageListItem.text; } messageListItem.num = 200 + trait; if (message_search(&trait_message_file, &messageListItem)) { trait_data[trait].description = messageListItem.text; } } // NOTE: Uninline. trait_reset(); return true; } // 0x4B3ADC void trait_reset() { for (int index = 0; index < TRAITS_MAX_SELECTED_COUNT; index++) { pc_trait[index] = -1; } } // 0x4B3AF8 void trait_exit() { message_exit(&trait_message_file); } // Loads trait system state from save game. // // 0x4B3B08 int trait_load(File* stream) { return db_freadIntCount(stream, pc_trait, TRAITS_MAX_SELECTED_COUNT); } // Saves trait system state to save game. // // 0x4B3B28 int trait_save(File* stream) { return db_fwriteIntCount(stream, pc_trait, TRAITS_MAX_SELECTED_COUNT); } // Sets selected traits. // // 0x4B3B48 void trait_set(int trait1, int trait2) { pc_trait[0] = trait1; pc_trait[1] = trait2; } // Returns selected traits. // // 0x4B3B54 void trait_get(int* trait1, int* trait2) { *trait1 = pc_trait[0]; *trait2 = pc_trait[1]; } // Returns a name of the specified trait, or `NULL` if the specified trait is // out of range. // // 0x4B3B68 char* trait_name(int trait) { return trait >= 0 && trait < TRAIT_COUNT ? trait_data[trait].name : NULL; } // Returns a description of the specified trait, or `NULL` if the specified // trait is out of range. // // 0x4B3B88 char* trait_description(int trait) { return trait >= 0 && trait < TRAIT_COUNT ? trait_data[trait].description : NULL; } // Return an art ID of the specified trait, or `0` if the specified trait is // out of range. // // 0x4B3BA8 int trait_pic(int trait) { return trait >= 0 && trait < TRAIT_COUNT ? trait_data[trait].frmId : 0; } // Returns `true` if the specified trait is selected. // // 0x4B3BC8 bool trait_level(int trait) { return pc_trait[0] == trait || pc_trait[1] == trait; } // Returns stat modifier depending on selected traits. // // 0x4B3C7C int trait_adjust_stat(int stat) { int modifier = 0; switch (stat) { case STAT_STRENGTH: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } if (trait_level(TRAIT_BRUISER)) { modifier += 2; } break; case STAT_PERCEPTION: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } break; case STAT_ENDURANCE: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } break; case STAT_CHARISMA: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } break; case STAT_INTELLIGENCE: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } break; case STAT_AGILITY: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } if (trait_level(TRAIT_SMALL_FRAME)) { modifier += 1; } break; case STAT_LUCK: if (trait_level(TRAIT_GIFTED)) { modifier += 1; } break; case STAT_MAXIMUM_ACTION_POINTS: if (trait_level(TRAIT_BRUISER)) { modifier -= 2; } break; case STAT_ARMOR_CLASS: if (trait_level(TRAIT_KAMIKAZE)) { modifier -= stat_get_base_direct(obj_dude, STAT_ARMOR_CLASS); } break; case STAT_MELEE_DAMAGE: if (trait_level(TRAIT_HEAVY_HANDED)) { modifier += 4; } break; case STAT_CARRY_WEIGHT: if (trait_level(TRAIT_SMALL_FRAME)) { modifier -= 10 * stat_get_base_direct(obj_dude, STAT_STRENGTH); } break; case STAT_SEQUENCE: if (trait_level(TRAIT_KAMIKAZE)) { modifier += 5; } break; case STAT_HEALING_RATE: if (trait_level(TRAIT_FAST_METABOLISM)) { modifier += 2; } break; case STAT_CRITICAL_CHANCE: if (trait_level(TRAIT_FINESSE)) { modifier += 10; } break; case STAT_BETTER_CRITICALS: if (trait_level(TRAIT_HEAVY_HANDED)) { modifier -= 30; } break; case STAT_RADIATION_RESISTANCE: if (trait_level(TRAIT_FAST_METABOLISM)) { modifier -= -stat_get_base_direct(obj_dude, STAT_RADIATION_RESISTANCE); } break; case STAT_POISON_RESISTANCE: if (trait_level(TRAIT_FAST_METABOLISM)) { modifier -= -stat_get_base_direct(obj_dude, STAT_POISON_RESISTANCE); } break; } return modifier; } // Returns skill modifier depending on selected traits. // // 0x4B40FC int trait_adjust_skill(int skill) { int modifier = 0; if (trait_level(TRAIT_GIFTED)) { modifier -= 10; } if (trait_level(TRAIT_GOOD_NATURED)) { switch (skill) { case SKILL_SMALL_GUNS: case SKILL_BIG_GUNS: case SKILL_ENERGY_WEAPONS: case SKILL_UNARMED: case SKILL_MELEE_WEAPONS: case SKILL_THROWING: modifier -= 10; break; case SKILL_FIRST_AID: case SKILL_DOCTOR: case SKILL_SPEECH: case SKILL_BARTER: modifier += 15; break; } } return modifier; } ================================================ FILE: src/game/trait.h ================================================ #ifndef FALLOUT_GAME_TRAIT_H_ #define FALLOUT_GAME_TRAIT_H_ #include #include "plib/db/db.h" #include "game/trait_defs.h" int trait_init(); void trait_reset(); void trait_exit(); int trait_load(File* stream); int trait_save(File* stream); void trait_set(int trait1, int trait2); void trait_get(int* trait1, int* trait2); char* trait_name(int trait); char* trait_description(int trait); int trait_pic(int trait); bool trait_level(int trait); int trait_adjust_stat(int stat); int trait_adjust_skill(int skill); #endif /* FALLOUT_GAME_TRAIT_H_ */ ================================================ FILE: src/game/trait_defs.h ================================================ #ifndef TRAIT_DEFS #define TRAIT_DEFS // The maximum number of traits a player is allowed to select. #define TRAITS_MAX_SELECTED_COUNT 2 // Available traits. typedef enum Trait { TRAIT_FAST_METABOLISM, TRAIT_BRUISER, TRAIT_SMALL_FRAME, TRAIT_ONE_HANDER, TRAIT_FINESSE, TRAIT_KAMIKAZE, TRAIT_HEAVY_HANDED, TRAIT_FAST_SHOT, TRAIT_BLOODY_MESS, TRAIT_JINXED, TRAIT_GOOD_NATURED, TRAIT_CHEM_RELIANT, TRAIT_CHEM_RESISTANT, TRAIT_SEX_APPEAL, TRAIT_SKILLED, TRAIT_GIFTED, TRAIT_COUNT, } Trait; #endif /* TRAIT_DEFS */ ================================================ FILE: src/game/trap.c ================================================ #include "game/trap.h" #include #include "plib/gnw/debug.h" #include "plib/gnw/gnw.h" typedef struct TrapEntry { int address; int name; int field_8; int field_C; } TrapEntry; typedef struct DuplicateEntry { int address; int field_4; int size; int name; int field_10; } DuplicateEntry; static void duplicate_report(int trap, int offset, const char* file, int line); static void heap_report(int trap, int address, const char* file, int line); // NOTE: Probably |trap_list_data|. // // 0x51DC44 static TrapEntry* off_51DC44 = NULL; // NOTE: Probably |trap_list_duplicate|. // // 0x66BE48 static DuplicateEntry stru_66BE48[512]; // 0x4B4190 void trap_exit() { } // 0x4B4190 void trap_init() { } // 0x4B43A4 static void trap_report(int trap, int address, const char* file, int line) { TrapEntry* entry; entry = &(off_51DC44[trap]); debug_printf("TRAPPED A STOMP ERROR:\n"); debug_printf("Stomp caught by check on line %d", line); debug_printf(" of module %s.\n", file); debug_printf("Data stomped was in trap %s", entry->name); debug_printf(" at address %p.\n", entry->address); debug_printf("See comment in trap.c for suggestions on better"); debug_printf(" isolating the stomp bug.\n"); GNWSystemError("STOMPED!"); exit(1); } // 0x4B4430 static void duplicate_report(int trap, int offset, const char* file, int line) { DuplicateEntry* entry; entry = &(stru_66BE48[trap]); debug_printf("TRAPPED A STOMP ERROR:\n"); debug_printf("Stomp caught by check on line %d", line); debug_printf(" of module %s.\n", file); debug_printf("Data stomped was in trap %s", entry->name); debug_printf(" at address %p.\n", entry->address + offset); debug_printf("This is duplicate trap number %d", trap); debug_printf(" at an internal offset of %d.\n", offset); debug_printf("Trap size is %d.\n", entry->size); debug_printf("See comment in trap.c for suggestions on better"); debug_printf(" isolating the stomp bug.\n"); GNWSystemError("STOMPED!"); exit(1); } // 0x4B44F0 static void heap_report(int trap, int address, const char* file, int line) { debug_printf("TRAPPED A STOMP ERROR:\n"); debug_printf("Stomp caught by check on line %d", line); debug_printf(" of module %s.\n", file); debug_printf("Data stomped was in heap trap number %d", trap); debug_printf(" at address %p.\n", address); debug_printf("See comment in trap.c for suggestions on better"); debug_printf(" isolating the stomp bug.\n"); GNWSystemError("STOMPED!"); exit(1); } ================================================ FILE: src/game/trap.h ================================================ #ifndef FALLOUT_GAME_TRAP_H_ #define FALLOUT_GAME_TRAP_H_ void trap_exit(); void trap_init(); #endif /* FALLOUT_GAME_TRAP_H_ */ ================================================ FILE: src/game/version.c ================================================ #include "game/version.h" #include // 0x4B4580 void getverstr(char* dest) { sprintf(dest, "FALLOUT II %d.%02d", VERSION_MAJOR, VERSION_MINOR); } ================================================ FILE: src/game/version.h ================================================ #ifndef FALLOUT_GAME_VERSION_H_ #define FALLOUT_GAME_VERSION_H_ // The size of buffer for version string. #define VERSION_MAX 32 #define VERSION_MAJOR 1 #define VERSION_MINOR 2 #define VERSION_RELEASE 'R' #define VERSION_BUILD_TIME "Dec 11 1998 16:54:30" void getverstr(char* dest); #endif /* FALLOUT_GAME_VERSION_H_ */ ================================================ FILE: src/game/wordwrap.c ================================================ #include "game/wordwrap.h" #include #include #include #include "plib/gnw/text.h" // 0x4BC6F0 int word_wrap(const char* string, int width, short* breakpoints, short* breakpointsLengthPtr) { breakpoints[0] = 0; *breakpointsLengthPtr = 1; for (int index = 1; index < WORD_WRAP_MAX_COUNT; index++) { breakpoints[index] = -1; } if (text_max() > width) { return -1; } if (text_width(string) < width) { breakpoints[*breakpointsLengthPtr] = (short)strlen(string); *breakpointsLengthPtr += 1; return 0; } int gap = text_spacing(); int accum = 0; const char* prevSpaceOrHyphen = NULL; const char* pch = string; while (*pch != '\0') { accum += gap + text_char_width(*pch & 0xFF); if (accum <= width) { // NOTE: quests.txt #807 uses extended ascii. if (isspace(*pch & 0xFF) || *pch == '-') { prevSpaceOrHyphen = pch; } } else { if (*breakpointsLengthPtr == WORD_WRAP_MAX_COUNT) { return -1; } if (prevSpaceOrHyphen != NULL) { // Word wrap. breakpoints[*breakpointsLengthPtr] = prevSpaceOrHyphen - string + 1; *breakpointsLengthPtr += 1; pch = prevSpaceOrHyphen; } else { // Character wrap. breakpoints[*breakpointsLengthPtr] = pch - string; *breakpointsLengthPtr += 1; pch--; } prevSpaceOrHyphen = NULL; accum = 0; } pch++; } if (*breakpointsLengthPtr == WORD_WRAP_MAX_COUNT) { return -1; } breakpoints[*breakpointsLengthPtr] = pch - string + 1; *breakpointsLengthPtr += 1; return 0; } ================================================ FILE: src/game/wordwrap.h ================================================ #ifndef FALLOUT_GAME_WORDWRAP_H_ #define FALLOUT_GAME_WORDWRAP_H_ #define WORD_WRAP_MAX_COUNT 64 int word_wrap(const char* string, int width, short* breakpoints, short* breakpointsLengthPtr); #endif /* FALLOUT_GAME_WORDWRAP_H_ */ ================================================ FILE: src/game/worldmap.c ================================================ #include "game/worldmap.h" #include #include #include #include "game/anim.h" #include "game/art.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "game/config.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "game/cycle.h" #include "plib/db/db.h" #include "game/bmpdlog.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "game/display.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gmouse.h" #include "game/gmovie.h" #include "game/gsound.h" #include "game/intface.h" #include "game/item.h" #include "game/map_defs.h" #include "plib/gnw/memory.h" #include "game/message.h" #include "game/object_types.h" #include "game/object.h" #include "game/party.h" #include "game/perk.h" #include "game/protinst.h" #include "game/queue.h" #include "game/roll.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/strparse.h" #include "plib/gnw/text.h" #include "game/tile.h" #include "plib/gnw/gnw.h" #define CITY_NAME_SIZE (40) #define TILE_WALK_MASK_NAME_SIZE (40) #define ENTRANCE_LIST_CAPACITY (10) #define MAP_AMBIENT_SOUND_EFFECTS_CAPACITY (6) #define MAP_STARTING_POINTS_CAPACITY (15) #define SUBTILE_GRID_WIDTH (7) #define SUBTILE_GRID_HEIGHT (6) #define ENCOUNTER_ENTRY_SPECIAL (0x01) #define ENCOUNTER_SUBINFO_DEAD (0x01) #define WM_WINDOW_DIAL_X (532) #define WM_WINDOW_DIAL_Y (48) #define WM_TOWN_LIST_SCROLL_UP_X (480) #define WM_TOWN_LIST_SCROLL_UP_Y (137) #define WM_TOWN_LIST_SCROLL_DOWN_X (WM_TOWN_LIST_SCROLL_UP_X) #define WM_TOWN_LIST_SCROLL_DOWN_Y (152) #define WM_WINDOW_GLOBE_OVERLAY_X (495) #define WM_WINDOW_GLOBE_OVERLAY_Y (330) #define WM_WINDOW_CAR_X (514) #define WM_WINDOW_CAR_Y (336) #define WM_WINDOW_CAR_OVERLAY_X (499) #define WM_WINDOW_CAR_OVERLAY_Y (330) #define WM_WINDOW_CAR_FUEL_BAR_X (500) #define WM_WINDOW_CAR_FUEL_BAR_Y (339) #define WM_WINDOW_CAR_FUEL_BAR_HEIGHT (70) #define WM_TOWN_WORLD_SWITCH_X (519) #define WM_TOWN_WORLD_SWITCH_Y (439) #define WM_TILE_WIDTH (350) #define WM_TILE_HEIGHT (300) #define WM_SUBTILE_SIZE (50) #define WM_WINDOW_WIDTH (640) #define WM_WINDOW_HEIGHT (480) #define WM_VIEW_X (22) #define WM_VIEW_Y (21) #define WM_VIEW_WIDTH (450) #define WM_VIEW_HEIGHT (443) typedef enum EncounterFormationType { ENCOUNTER_FORMATION_TYPE_SURROUNDING, ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE, ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE, ENCOUNTER_FORMATION_TYPE_WEDGE, ENCOUNTER_FORMATION_TYPE_CONE, ENCOUNTER_FORMATION_TYPE_HUDDLE, ENCOUNTER_FORMATION_TYPE_COUNT, } EncounterFormationType; typedef enum EncounterFrequencyType { ENCOUNTER_FREQUENCY_TYPE_NONE, ENCOUNTER_FREQUENCY_TYPE_RARE, ENCOUNTER_FREQUENCY_TYPE_UNCOMMON, ENCOUNTER_FREQUENCY_TYPE_COMMON, ENCOUNTER_FREQUENCY_TYPE_FREQUENT, ENCOUNTER_FREQUENCY_TYPE_FORCED, ENCOUNTER_FREQUENCY_TYPE_COUNT, } EncounterFrequencyType; typedef enum EncounterSceneryType { ENCOUNTER_SCENERY_TYPE_NONE, ENCOUNTER_SCENERY_TYPE_LIGHT, ENCOUNTER_SCENERY_TYPE_NORMAL, ENCOUNTER_SCENERY_TYPE_HEAVY, ENCOUNTER_SCENERY_TYPE_COUNT, } EncounterSceneryType; typedef enum EncounterSituation { ENCOUNTER_SITUATION_NOTHING, ENCOUNTER_SITUATION_AMBUSH, ENCOUNTER_SITUATION_FIGHTING, ENCOUNTER_SITUATION_AND, ENCOUNTER_SITUATION_COUNT, } EncounterSituation; typedef enum EncounterLogicalOperator { ENCOUNTER_LOGICAL_OPERATOR_NONE, ENCOUNTER_LOGICAL_OPERATOR_AND, ENCOUNTER_LOGICAL_OPERATOR_OR, } EncounterLogicalOperator; typedef enum EncounterConditionType { ENCOUNTER_CONDITION_TYPE_NONE = 0, ENCOUNTER_CONDITION_TYPE_GLOBAL = 1, ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS = 2, ENCOUNTER_CONDITION_TYPE_RANDOM = 3, ENCOUNTER_CONDITION_TYPE_PLAYER = 4, ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED = 5, ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY = 6, } EncounterConditionType; typedef enum EncounterConditionalOperator { ENCOUNTER_CONDITIONAL_OPERATOR_NONE, ENCOUNTER_CONDITIONAL_OPERATOR_EQUAL, ENCOUNTER_CONDITIONAL_OPERATOR_NOT_EQUAL, ENCOUNTER_CONDITIONAL_OPERATOR_LESS_THAN, ENCOUNTER_CONDITIONAL_OPERATOR_GREATER_THAN, ENCOUNTER_CONDITIONAL_OPERATOR_COUNT, } EncounterConditionalOperator; typedef enum Daytime { DAY_PART_MORNING, DAY_PART_AFTERNOON, DAY_PART_NIGHT, DAY_PART_COUNT, } Daytime; typedef enum LockState { LOCK_STATE_UNLOCKED, LOCK_STATE_LOCKED, } LockState; typedef enum SubtileState { SUBTILE_STATE_UNKNOWN, SUBTILE_STATE_KNOWN, SUBTILE_STATE_VISITED, } SubtileState; typedef enum WorldMapEncounterFrm { WORLD_MAP_ENCOUNTER_FRM_RANDOM_BRIGHT, WORLD_MAP_ENCOUNTER_FRM_RANDOM_DARK, WORLD_MAP_ENCOUNTER_FRM_SPECIAL_BRIGHT, WORLD_MAP_ENCOUNTER_FRM_SPECIAL_DARK, WORLD_MAP_ENCOUNTER_FRM_COUNT, } WorldMapEncounterFrm; typedef enum WorldmapArrowFrm { WORLDMAP_ARROW_FRM_NORMAL, WORLDMAP_ARROW_FRM_PRESSED, WORLDMAP_ARROW_FRM_COUNT, } WorldmapArrowFrm; typedef enum CitySize { CITY_SIZE_SMALL, CITY_SIZE_MEDIUM, CITY_SIZE_LARGE, CITY_SIZE_COUNT, } CitySize; typedef struct EntranceInfo { int state; int x; int y; int map; int elevation; int tile; int rotation; } EntranceInfo; typedef struct CityInfo { char name[CITY_NAME_SIZE]; int areaId; int x; int y; int size; int state; int lockState; int visitedState; int mapFid; int labelFid; int entrancesLength; EntranceInfo entrances[ENTRANCE_LIST_CAPACITY]; } CityInfo; typedef struct MapAmbientSoundEffectInfo { char name[40]; int chance; } MapAmbientSoundEffectInfo; typedef struct MapStartPointInfo { int elevation; int tile; int field_8; } MapStartPointInfo; typedef struct MapInfo { char lookupName[40]; int field_28; int field_2C; char mapFileName[40]; char music[40]; int flags; int ambientSoundEffectsLength; MapAmbientSoundEffectInfo ambientSoundEffects[MAP_AMBIENT_SOUND_EFFECTS_CAPACITY]; int startPointsLength; MapStartPointInfo startPoints[MAP_STARTING_POINTS_CAPACITY]; } MapInfo; typedef struct Terrain { char lookupName[40]; int type; int mapsLength; int maps[20]; } Terrain; typedef struct EncounterConditionEntry { int type; int conditionalOperator; int param; int value; } EncounterConditionEntry; typedef struct EncounterCondition { int entriesLength; EncounterConditionEntry entries[3]; int logicalOperators[2]; } EncounterCondition; typedef struct ENCOUNTER_ENTRY_ENC { int minQuantity; // min int maxQuantity; // max int field_8; int situation; } ENCOUNTER_ENTRY_ENC; typedef struct EncounterEntry { int flags; int map; int scenery; int chance; int counter; EncounterCondition condition; int field_50; ENCOUNTER_ENTRY_ENC field_54[6]; } EncounterEntry; typedef struct EncounterTable { char lookupName[40]; int field_28; int mapsLength; int maps[6]; int field_48; int entriesLength; EncounterEntry entries[41]; } EncounterTable; typedef struct ENC_BASE_TYPE_38_48 { int pid; int minimumQuantity; int maximumQuantity; bool isEquipped; } ENC_BASE_TYPE_38_48; typedef struct ENC_BASE_TYPE_38 { char field_0[40]; int field_28; int field_2C; int ratio; int pid; int flags; int distance; int tile; int itemsLength; ENC_BASE_TYPE_38_48 items[10]; int team; int script; EncounterCondition condition; } ENC_BASE_TYPE_38; typedef struct ENC_BASE_TYPE { char name[40]; int position; int spacing; int distance; int field_34; ENC_BASE_TYPE_38 field_38[10]; } ENC_BASE_TYPE; typedef struct SubtileInfo { int terrain; int field_4; int encounterChance[DAY_PART_COUNT]; int encounterType; int state; } SubtileInfo; // A worldmap tile is 7x6 area, thus consisting of 42 individual subtiles. typedef struct TileInfo { int fid; CacheEntry* handle; unsigned char* data; char walkMaskName[TILE_WALK_MASK_NAME_SIZE]; unsigned char* walkMaskData; int encounterDifficultyModifier; SubtileInfo subtiles[SUBTILE_GRID_HEIGHT][SUBTILE_GRID_WIDTH]; } TileInfo; typedef struct CitySizeDescription { int fid; int width; int height; CacheEntry* handle; unsigned char* data; } CitySizeDescription; typedef struct WmGenData { bool mousePressed; bool didMeetFrankHorrigan; int currentAreaId; int worldPosX; int worldPosY; SubtileInfo* currentSubtile; int dword_672E18; bool isWalking; int walkDestinationX; int walkDestinationY; int walkDistance; int walkLineDelta; int walkLineDeltaMainAxisStep; int walkLineDeltaCrossAxisStep; int walkWorldPosMainAxisStepX; int walkWorldPosCrossAxisStepX; int walkWorldPosMainAxisStepY; int walkWorldPosCrossAxisStepY; int encounterIconIsVisible; int encounterMapId; int encounterTableId; int encounterEntryId; int encounterCursorId; int oldWorldPosX; int oldWorldPosY; bool isInCar; int currentCarAreaId; int carFuel; CacheEntry* carImageFrmHandle; Art* carImageFrm; int carImageFrmWidth; int carImageFrmHeight; int carImageCurrentFrameIndex; CacheEntry* hotspotNormalFrmHandle; unsigned char* hotspotNormalFrmData; CacheEntry* hotspotPressedFrmHandle; unsigned char* hotspotPressedFrmData; int hotspotFrmWidth; int hotspotFrmHeight; CacheEntry* destinationMarkerFrmHandle; unsigned char* destinationMarkerFrmData; int destinationMarkerFrmWidth; int destinationMarkerFrmHeight; CacheEntry* locationMarkerFrmHandle; unsigned char* locationMarkerFrmData; int locationMarkerFrmWidth; int locationMarkerFrmHeight; CacheEntry* encounterCursorFrmHandle[WORLD_MAP_ENCOUNTER_FRM_COUNT]; unsigned char* encounterCursorFrmData[WORLD_MAP_ENCOUNTER_FRM_COUNT]; int encounterCursorFrmWidths[WORLD_MAP_ENCOUNTER_FRM_COUNT]; int encounterCursorFrmHeights[WORLD_MAP_ENCOUNTER_FRM_COUNT]; int viewportMaxX; int viewportMaxY; CacheEntry* tabsBackgroundFrmHandle; int tabsBackgroundFrmWidth; int tabsBackgroundFrmHeight; int tabsOffsetY; unsigned char* tabsBackgroundFrmData; CacheEntry* tabsBorderFrmHandle; unsigned char* tabsBorderFrmData; CacheEntry* dialFrmHandle; int dialFrmWidth; int dialFrmHeight; int dialFrmCurrentFrameIndex; Art* dialFrm; CacheEntry* carImageOverlayFrmHandle; int carImageOverlayFrmWidth; int carImageOverlayFrmHeight; unsigned char* carImageOverlayFrmData; CacheEntry* globeOverlayFrmHandle; int globeOverlayFrmWidth; int globeOverlayFrmHeight; unsigned char* globeOverlayFrmData; int oldTabsOffsetY; int tabsScrollingDelta; CacheEntry* littleRedButtonNormalFrmHandle; CacheEntry* littleRedButtonPressedFrmHandle; unsigned char* littleRedButtonNormalFrmData; unsigned char* littleRedButtonPressedFrmData; CacheEntry* scrollUpButtonFrmHandle[WORLDMAP_ARROW_FRM_COUNT]; int scrollUpButtonFrmWidth; int scrollUpButtonFrmHeight; unsigned char* scrollUpButtonFrmData[WORLDMAP_ARROW_FRM_COUNT]; CacheEntry* scrollDownButtonFrmHandle[WORLDMAP_ARROW_FRM_COUNT]; int scrollDownButtonFrmWidth; int scrollDownButtonFrmHeight; unsigned char* scrollDownButtonFrmData[WORLDMAP_ARROW_FRM_COUNT]; CacheEntry* monthsFrmHandle; Art* monthsFrm; CacheEntry* numbersFrmHandle; Art* numbersFrm; int oldFont; } WmGenData; static void wmSetFlags(int* flagsPtr, int flag, int value); static int wmGenDataInit(); static int wmGenDataReset(); static int wmWorldMapSaveTempData(); static int wmWorldMapLoadTempData(); static int wmConfigInit(); static int wmReadEncounterType(Config* config, char* lookupName, char* sectionKey); static int wmParseEncounterTableIndex(EncounterEntry* entry, char* string); static int wmParseEncounterSubEncStr(EncounterEntry* encounterEntry, char** stringPtr); static int wmParseFindSubEncTypeMatch(char* str, int* valuePtr); static int wmFindEncBaseTypeMatch(char* str, int* valuePtr); static int wmReadEncBaseType(char* name, int* valuePtr); static int wmParseEncBaseSubTypeStr(ENC_BASE_TYPE_38* ptr, char** stringPtr); static int wmEncBaseTypeSlotInit(ENC_BASE_TYPE* entry); static int wmEncBaseSubTypeSlotInit(ENC_BASE_TYPE_38* entry); static int wmEncounterSubEncSlotInit(ENCOUNTER_ENTRY_ENC* entry); static int wmEncounterTypeSlotInit(EncounterEntry* entry); static int wmEncounterTableSlotInit(EncounterTable* encounterTable); static int wmTileSlotInit(TileInfo* tile); static int wmTerrainTypeSlotInit(Terrain* terrain); static int wmConditionalDataInit(EncounterCondition* condition); static int wmParseTerrainTypes(Config* config, char* string); static int wmParseTerrainRndMaps(Config* config, Terrain* terrain); static int wmParseSubTileInfo(TileInfo* tile, int row, int column, char* string); static int wmParseFindEncounterTypeMatch(char* string, int* valuePtr); static int wmParseFindTerrainTypeMatch(char* string, int* valuePtr); static int wmParseEncounterItemType(char** stringPtr, ENC_BASE_TYPE_38_48* a2, int* a3, const char* delim); static int wmParseItemType(char* string, ENC_BASE_TYPE_38_48* ptr); static int wmParseConditional(char** stringPtr, const char* a2, EncounterCondition* condition); static int wmParseSubConditional(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr); static int wmParseConditionalEval(char** stringPtr, int* conditionalOperatorPtr); static int wmAreaSlotInit(CityInfo* area); static int wmAreaInit(); static int wmParseFindMapIdxMatch(char* string, int* valuePtr); static int wmEntranceSlotInit(EntranceInfo* entrance); static int wmMapSlotInit(MapInfo* map); static int wmMapInit(); static int wmRStartSlotInit(MapStartPointInfo* rsp); static int wmMatchEntranceFromMap(int areaIdx, int mapIdx, int* entranceIdxPtr); static int wmMatchEntranceElevFromMap(int areaIdx, int mapIdx, int elevation, int* entranceIdxPtr); static int wmMatchAreaFromMap(int mapIdx, int* areaIdxPtr); static int wmWorldMapFunc(int a1); static int wmInterfaceCenterOnParty(); static void wmCheckGameEvents(); static int wmRndEncounterOccurred(); static int wmPartyFindCurSubTile(); static int wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtilePtr); static int wmFindCurTileFromPos(int x, int y, TileInfo** tilePtr); static int wmRndEncounterPick(); static int wmSetupCritterObjs(int type_idx, Object** critterPtr, int critterCount); static int wmSetupRndNextTileNumInit(ENC_BASE_TYPE* a1); static int wmSetupRndNextTileNum(ENC_BASE_TYPE* a1, ENC_BASE_TYPE_38* a2, int* out_tile_num); static bool wmEvalConditional(EncounterCondition* a1, int* a2); static bool wmEvalSubConditional(int operand1, int condionalOperator, int operand2); static bool wmGameTimeIncrement(int a1); static int wmGrabTileWalkMask(int tile); static bool wmWorldPosInvalid(int x, int y); static void wmPartyInitWalking(int x, int y); static void wmPartyWalkingStep(); static void wmInterfaceScrollTabsStart(int delta); static void wmInterfaceScrollTabsStop(); static void wmInterfaceScrollTabsUpdate(); static int wmInterfaceInit(); static int wmInterfaceExit(); static int wmInterfaceScroll(int dx, int dy, bool* successPtr); static int wmInterfaceScrollPixel(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh); static void wmMouseBkProc(); static int wmMarkSubTileOffsetVisited(int tile, int subtileX, int subtileY, int offsetX, int offsetY); static int wmMarkSubTileOffsetKnown(int tile, int subtileX, int subtileY, int offsetX, int offsetY); static int wmMarkSubTileOffsetVisitedFunc(int tile, int subtileX, int subtileY, int offsetX, int offsetY, int subtileState); static void wmMarkSubTileRadiusVisited(int x, int y); static int wmTileGrabArt(int tileIdx); static int wmInterfaceRefresh(); static void wmInterfaceRefreshDate(bool shouldRefreshWindow); static int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr); static int wmInterfaceDrawCircleOverlay(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y); static void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch); static int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6); static int wmDrawCursorStopped(); static bool wmCursorIsVisible(); static int wmGetAreaName(CityInfo* city, char* name); static void wmMarkAllSubTiles(int state); static int wmTownMapFunc(int* mapIdxPtr); static int wmTownMapInit(); static int wmTownMapRefresh(); static int wmTownMapExit(); static int wmRefreshInterfaceOverlay(bool shouldRefreshWindow); static void wmInterfaceRefreshCarFuel(); static int wmRefreshTabs(); static int wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr); static int wmTabsCompareNames(const void* a1, const void* a2); static int wmFreeTabsLabelList(int** quickDestinationsListPtr, int* quickDestinationsLengthPtr); static void wmRefreshInterfaceDial(bool shouldRefreshWindow); static void wmInterfaceDialSyncTime(bool shouldRefreshWindow); static int wmAreaFindFirstValidMap(int* mapIdxPtr); static bool cityIsValid(int areaIdx); // 0x4BC878 static const char* gWorldmapEncDefaultMsg[2] = { "You detect something up ahead.", "Do you wish to encounter it?", }; // 0x50EE44 static char _aCricket[] = "cricket"; // 0x50EE4C static char _aCricket1[] = "cricket1"; // 0x51DD88 static const char* wmStateStrs[2] = { "off", "on" }; // 0x51DD90 static const char* wmYesNoStrs[2] = { "no", "yes", }; // 0x51DD98 static const char* wmFreqStrs[ENCOUNTER_FREQUENCY_TYPE_COUNT] = { "none", "rare", "uncommon", "common", "frequent", "forced", }; // 0x51DDB0 static const char* wmFillStrs[9] = { "no_fill", "fill_n", "fill_s", "fill_e", "fill_w", "fill_nw", "fill_ne", "fill_sw", "fill_se", }; // 0x51DDD4 static const char* wmSceneryStrs[ENCOUNTER_SCENERY_TYPE_COUNT] = { "none", "light", "normal", "heavy", }; // 0x51DDE4 static Terrain* wmTerrainTypeList = NULL; // 0x51DDE8 static int wmMaxTerrainTypes = 0; // 0x51DDEC static TileInfo* wmTileInfoList = NULL; // 0x51DDF0 static int wmMaxTileNum = 0; // The width of worldmap grid in tiles. // // There is no separate variable for grid height, instead its calculated as // [wmMaxTileNum] / [gWorldmapTilesGridWidth]. // // num_horizontal_tiles // 0x51DDF4 static int wmNumHorizontalTiles = 0; // 0x51DDF8 static CityInfo* wmAreaInfoList = NULL; // 0x51DDFC static int wmMaxAreaNum = 0; // 0x51DE00 static const char* wmAreaSizeStrs[CITY_SIZE_COUNT] = { "small", "medium", "large", }; // 0x51DE0C static MapInfo* wmMapInfoList = NULL; // 0x51DE10 static int wmMaxMapNum = 0; // 0x51DE14 static int wmBkWin = -1; // 0x51DE18 static CacheEntry* wmBkKey = INVALID_CACHE_ENTRY; // 0x51DE1C static int wmBkWidth = 0; // 0x51DE20 static int wmBkHeight = 0; // 0x51DE24 static unsigned char* wmBkWinBuf = NULL; // 0x51DE28 static unsigned char* wmBkArtBuf = NULL; // 0x51DE2C static int wmWorldOffsetX = 0; // 0x51DE30 static int wmWorldOffsetY = 0; // 0x51DE34 unsigned char* circleBlendTable = NULL; // 0x51DE38 static int wmInterfaceWasInitialized = 0; // 0x51DE3C static const char* wmEncOpStrs[ENCOUNTER_SITUATION_COUNT] = { "nothing", "ambush", "fighting", "and", }; // 0x51DE4C static const char* wmConditionalOpStrs[ENCOUNTER_CONDITIONAL_OPERATOR_COUNT] = { "_", "==", "!=", "<", ">", }; // 0x51DE64 static const char* wmConditionalQualifierStrs[2] = { "and", "or", }; // 0x51DE6C static const char* wmFormationStrs[ENCOUNTER_FORMATION_TYPE_COUNT] = { "surrounding", "straight_line", "double_line", "wedge", "cone", "huddle", }; // 0x51DE84 static const int wmRndCursorFids[WORLD_MAP_ENCOUNTER_FRM_COUNT] = { 154, 155, 438, 439, }; // 0x51DE94 static int* wmLabelList = NULL; // 0x51DE98 static int wmLabelCount = 0; // 0x51DE9C static int wmTownMapCurArea = -1; // 0x51DEA0 static unsigned int wmLastRndTime = 0; // 0x51DEA4 static int wmRndIndex = 0; // 0x51DEA8 static int wmRndCallCount = 0; // 0x51DEB8 static unsigned char* wmTownBuffer = NULL; // 0x51DEBC static CacheEntry* wmTownKey = INVALID_CACHE_ENTRY; // 0x51DEC0 static int wmTownWidth = 0; // 0x51DEC4 static int wmTownHeight = 0; // 0x51DEC8 static char* wmRemapSfxList[2] = { _aCricket, _aCricket1, }; // 0x672DB8 static int wmRndTileDirs[2]; // 0x672DC0 static int wmRndCenterTiles[2]; // 0x672DC8 static int wmRndCenterRotations[2]; // 0x672DD0 static int wmRndRotOffsets[2]; // Buttons for city entrances. // // 0x672DD8 static int wmTownMapButtonId[ENTRANCE_LIST_CAPACITY]; // NOTE: There are no symbols in |mapper2.exe| for the range between |wmGenData| // and |wmMsgFile| implying everything in between are fields of the large // struct. // // 0x672E00 static WmGenData wmGenData; // worldmap.msg // // 0x672FB0 static MessageList wmMsgFile; // 0x672FB8 static int wmFreqValues[6]; // 0x672FD0 static int wmRndOriginalCenterTile; // worldmap.txt // // 0x672FD4 static Config* pConfigCfg; // 0x672FD8 static int wmTownMapSubButtonIds[7]; // 0x672FF4 static ENC_BASE_TYPE* wmEncBaseTypeList; // 0x672FF8 static CitySizeDescription wmSphereData[CITY_SIZE_COUNT]; // 0x673034 static EncounterTable* wmEncounterTableList; // Number of enc_base_types. // // 0x673038 static int _wmMaxEncBaseTypes; // 0x67303C static int wmMaxEncounterInfoTables; // 0x4BC890 static void wmSetFlags(int* flagsPtr, int flag, int value) { if (value) { *flagsPtr |= flag; } else { *flagsPtr &= ~flag; } } // 0x4BC89C int wmWorldMap_init() { char path[MAX_PATH]; if (wmGenDataInit() == -1) { return -1; } if (!message_init(&wmMsgFile)) { return -1; } sprintf(path, "%s%s", msg_path, "worldmap.msg"); if (!message_load(&wmMsgFile, path)) { return -1; } if (wmConfigInit() == -1) { return -1; } wmGenData.viewportMaxX = WM_TILE_WIDTH * wmNumHorizontalTiles - WM_VIEW_WIDTH; wmGenData.viewportMaxY = WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles) - WM_VIEW_HEIGHT; circleBlendTable = getColorBlendTable(colorTable[992]); wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); wmWorldMapSaveTempData(); return 0; } // 0x4BC984 static int wmGenDataInit() { wmGenData.didMeetFrankHorrigan = false; wmGenData.currentAreaId = -1; wmGenData.worldPosX = 173; wmGenData.worldPosY = 122; wmGenData.currentSubtile = NULL; wmGenData.dword_672E18 = 0; wmGenData.isWalking = false; wmGenData.walkDestinationX = -1; wmGenData.walkDestinationY = -1; wmGenData.walkDistance = 0; wmGenData.walkLineDelta = 0; wmGenData.walkLineDeltaMainAxisStep = 0; wmGenData.walkLineDeltaCrossAxisStep = 0; wmGenData.walkWorldPosMainAxisStepX = 0; wmGenData.walkWorldPosMainAxisStepY = 0; wmGenData.walkWorldPosCrossAxisStepY = 0; wmGenData.encounterIconIsVisible = 0; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; wmGenData.encounterCursorId = -1; wmGenData.oldWorldPosX = 0; wmGenData.oldWorldPosY = 0; wmGenData.isInCar = false; wmGenData.currentCarAreaId = -1; wmGenData.carFuel = CAR_FUEL_MAX; wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageFrmWidth = 0; wmGenData.carImageFrmHeight = 0; wmGenData.carImageCurrentFrameIndex = 0; wmGenData.hotspotNormalFrmHandle = INVALID_CACHE_ENTRY; wmGenData.hotspotNormalFrmData = NULL; wmGenData.hotspotPressedFrmHandle = INVALID_CACHE_ENTRY; wmGenData.hotspotPressedFrmData = NULL; wmGenData.hotspotFrmWidth = 0; wmGenData.hotspotFrmHeight = 0; wmGenData.destinationMarkerFrmHandle = INVALID_CACHE_ENTRY; wmGenData.destinationMarkerFrmData = NULL; wmGenData.destinationMarkerFrmWidth = 0; wmGenData.destinationMarkerFrmHeight = 0; wmGenData.locationMarkerFrmHandle = INVALID_CACHE_ENTRY; wmGenData.locationMarkerFrmData = NULL; wmGenData.locationMarkerFrmWidth = 0; wmGenData.mousePressed = false; wmGenData.walkWorldPosCrossAxisStepX = 0; wmGenData.locationMarkerFrmHeight = 0; wmGenData.carImageFrm = NULL; for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) { wmGenData.encounterCursorFrmHandle[index] = INVALID_CACHE_ENTRY; wmGenData.encounterCursorFrmData[index] = NULL; wmGenData.encounterCursorFrmWidths[index] = 0; wmGenData.encounterCursorFrmHeights[index] = 0; } wmGenData.viewportMaxY = 0; wmGenData.tabsBackgroundFrmHandle = INVALID_CACHE_ENTRY; wmGenData.tabsBackgroundFrmData = NULL; wmGenData.tabsBackgroundFrmWidth = 0; wmGenData.tabsBackgroundFrmHeight = 0; wmGenData.tabsOffsetY = 0; wmGenData.tabsBorderFrmHandle = INVALID_CACHE_ENTRY; wmGenData.tabsBorderFrmData = 0; wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY; wmGenData.dialFrm = NULL; wmGenData.dialFrmWidth = 0; wmGenData.dialFrmHeight = 0; wmGenData.dialFrmCurrentFrameIndex = 0; wmGenData.carImageOverlayFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageOverlayFrmData = NULL; wmGenData.carImageOverlayFrmWidth = 0; wmGenData.carImageOverlayFrmHeight = 0; wmGenData.globeOverlayFrmHandle = INVALID_CACHE_ENTRY; wmGenData.globeOverlayFrmData = NULL; wmGenData.globeOverlayFrmWidth = 0; wmGenData.globeOverlayFrmHeight = 0; wmGenData.oldTabsOffsetY = 0; wmGenData.tabsScrollingDelta = 0; wmGenData.littleRedButtonNormalFrmHandle = INVALID_CACHE_ENTRY; wmGenData.littleRedButtonPressedFrmHandle = INVALID_CACHE_ENTRY; wmGenData.littleRedButtonNormalFrmData = NULL; wmGenData.littleRedButtonPressedFrmData = NULL; wmGenData.viewportMaxX = 0; for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { wmGenData.scrollDownButtonFrmHandle[index] = INVALID_CACHE_ENTRY; wmGenData.scrollUpButtonFrmHandle[index] = INVALID_CACHE_ENTRY; wmGenData.scrollUpButtonFrmData[index] = NULL; wmGenData.scrollDownButtonFrmData[index] = NULL; } wmGenData.scrollUpButtonFrmHeight = 0; wmGenData.scrollDownButtonFrmWidth = 0; wmGenData.scrollDownButtonFrmHeight = 0; wmGenData.monthsFrmHandle = INVALID_CACHE_ENTRY; wmGenData.monthsFrm = NULL; wmGenData.numbersFrmHandle = INVALID_CACHE_ENTRY; wmGenData.numbersFrm = NULL; wmGenData.scrollUpButtonFrmWidth = 0; return 0; } // 0x4BCBFC static int wmGenDataReset() { wmGenData.didMeetFrankHorrigan = false; wmGenData.currentSubtile = NULL; wmGenData.dword_672E18 = 0; wmGenData.isWalking = false; wmGenData.walkDistance = 0; wmGenData.walkLineDelta = 0; wmGenData.walkLineDeltaMainAxisStep = 0; wmGenData.walkLineDeltaCrossAxisStep = 0; wmGenData.walkWorldPosMainAxisStepX = 0; wmGenData.walkWorldPosMainAxisStepY = 0; wmGenData.walkWorldPosCrossAxisStepY = 0; wmGenData.encounterIconIsVisible = 0; wmGenData.mousePressed = false; wmGenData.currentAreaId = -1; wmGenData.worldPosX = 173; wmGenData.worldPosY = 122; wmGenData.walkDestinationX = -1; wmGenData.walkDestinationY = -1; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; wmGenData.encounterCursorId = -1; wmGenData.currentCarAreaId = -1; wmGenData.carFuel = CAR_FUEL_MAX; wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY; wmGenData.tabsBackgroundFrmHandle = INVALID_CACHE_ENTRY; wmGenData.tabsBorderFrmHandle = INVALID_CACHE_ENTRY; wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageOverlayFrmHandle = INVALID_CACHE_ENTRY; wmGenData.globeOverlayFrmHandle = INVALID_CACHE_ENTRY; wmGenData.littleRedButtonNormalFrmHandle = INVALID_CACHE_ENTRY; wmGenData.littleRedButtonPressedFrmHandle = INVALID_CACHE_ENTRY; wmGenData.walkWorldPosCrossAxisStepX = 0; wmGenData.oldWorldPosX = 0; wmGenData.oldWorldPosY = 0; wmGenData.isInCar = false; wmGenData.carImageFrmWidth = 0; wmGenData.carImageFrmHeight = 0; wmGenData.carImageCurrentFrameIndex = 0; wmGenData.tabsBackgroundFrmData = NULL; wmGenData.tabsBackgroundFrmHeight = 0; wmGenData.tabsOffsetY = 0; wmGenData.tabsBorderFrmData = 0; wmGenData.dialFrm = NULL; wmGenData.dialFrmWidth = 0; wmGenData.dialFrmHeight = 0; wmGenData.dialFrmCurrentFrameIndex = 0; wmGenData.carImageOverlayFrmData = NULL; wmGenData.carImageOverlayFrmWidth = 0; wmGenData.carImageOverlayFrmHeight = 0; wmGenData.globeOverlayFrmData = NULL; wmGenData.globeOverlayFrmWidth = 0; wmGenData.globeOverlayFrmHeight = 0; wmGenData.oldTabsOffsetY = 0; wmGenData.tabsScrollingDelta = 0; wmGenData.littleRedButtonNormalFrmData = NULL; wmGenData.littleRedButtonPressedFrmData = NULL; wmGenData.tabsBackgroundFrmWidth = 0; wmGenData.carImageFrm = NULL; for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { wmGenData.scrollUpButtonFrmData[index] = NULL; wmGenData.scrollDownButtonFrmHandle[index] = INVALID_CACHE_ENTRY; wmGenData.scrollDownButtonFrmData[index] = NULL; wmGenData.scrollUpButtonFrmHandle[index] = INVALID_CACHE_ENTRY; } wmGenData.monthsFrmHandle = INVALID_CACHE_ENTRY; wmGenData.numbersFrmHandle = INVALID_CACHE_ENTRY; wmGenData.scrollUpButtonFrmWidth = 0; wmGenData.scrollUpButtonFrmHeight = 0; wmGenData.scrollDownButtonFrmWidth = 0; wmGenData.scrollDownButtonFrmHeight = 0; wmGenData.monthsFrm = NULL; wmGenData.numbersFrm = NULL; wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); return 0; } // 0x4BCE00 void wmWorldMap_exit() { if (wmTerrainTypeList != NULL) { mem_free(wmTerrainTypeList); wmTerrainTypeList = NULL; } if (wmTileInfoList) { mem_free(wmTileInfoList); wmTileInfoList = NULL; } wmNumHorizontalTiles = 0; wmMaxTileNum = 0; if (wmEncounterTableList != NULL) { mem_free(wmEncounterTableList); wmEncounterTableList = NULL; } wmMaxEncounterInfoTables = 0; if (wmEncBaseTypeList != NULL) { mem_free(wmEncBaseTypeList); wmEncBaseTypeList = NULL; } _wmMaxEncBaseTypes = 0; if (wmAreaInfoList != NULL) { mem_free(wmAreaInfoList); wmAreaInfoList = NULL; } wmMaxAreaNum = 0; if (wmMapInfoList != NULL) { mem_free(wmMapInfoList); } wmMaxMapNum = 0; if (circleBlendTable != NULL) { freeColorBlendTable(colorTable[992]); circleBlendTable = NULL; } message_exit(&wmMsgFile); } // 0x4BCEF8 int wmWorldMap_reset() { wmWorldOffsetX = 0; wmWorldOffsetY = 0; wmWorldMapLoadTempData(); wmMarkAllSubTiles(0); return wmGenDataReset(); } // 0x4BCF28 int wmWorldMap_save(File* stream) { int i; int j; int k; EncounterTable* encounter_table; EncounterEntry* encounter_entry; if (fileWriteBool(stream, wmGenData.didMeetFrankHorrigan) == -1) return -1; if (db_fwriteInt(stream, wmGenData.currentAreaId) == -1) return -1; if (db_fwriteInt(stream, wmGenData.worldPosX) == -1) return -1; if (db_fwriteInt(stream, wmGenData.worldPosY) == -1) return -1; if (db_fwriteInt(stream, wmGenData.encounterIconIsVisible) == -1) return -1; if (db_fwriteInt(stream, wmGenData.encounterMapId) == -1) return -1; if (db_fwriteInt(stream, wmGenData.encounterTableId) == -1) return -1; if (db_fwriteInt(stream, wmGenData.encounterEntryId) == -1) return -1; if (fileWriteBool(stream, wmGenData.isInCar) == -1) return -1; if (db_fwriteInt(stream, wmGenData.currentCarAreaId) == -1) return -1; if (db_fwriteInt(stream, wmGenData.carFuel) == -1) return -1; if (db_fwriteInt(stream, wmMaxAreaNum) == -1) return -1; for (int cityIdx = 0; cityIdx < wmMaxAreaNum; cityIdx++) { CityInfo* cityInfo = &(wmAreaInfoList[cityIdx]); if (db_fwriteInt(stream, cityInfo->x) == -1) return -1; if (db_fwriteInt(stream, cityInfo->y) == -1) return -1; if (db_fwriteInt(stream, cityInfo->state) == -1) return -1; if (db_fwriteInt(stream, cityInfo->visitedState) == -1) return -1; if (db_fwriteInt(stream, cityInfo->entrancesLength) == -1) return -1; for (int entranceIdx = 0; entranceIdx < cityInfo->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(cityInfo->entrances[entranceIdx]); if (db_fwriteInt(stream, entrance->state) == -1) return -1; } } if (db_fwriteInt(stream, wmMaxTileNum) == -1) return -1; if (db_fwriteInt(stream, wmNumHorizontalTiles) == -1) return -1; for (int tileIndex = 0; tileIndex < wmMaxTileNum; tileIndex++) { TileInfo* tileInfo = &(wmTileInfoList[tileIndex]); for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { SubtileInfo* subtile = &(tileInfo->subtiles[column][row]); if (db_fwriteInt(stream, subtile->state) == -1) return -1; } } } k = 0; for (i = 0; i < wmMaxEncounterInfoTables; i++) { encounter_table = &(wmEncounterTableList[i]); for (j = 0; j < encounter_table->entriesLength; j++) { encounter_entry = &(encounter_table->entries[j]); if (encounter_entry->counter != -1) { k++; } } } if (db_fwriteInt(stream, k) == -1) return -1; for (i = 0; i < wmMaxEncounterInfoTables; i++) { encounter_table = &(wmEncounterTableList[i]); for (j = 0; j < encounter_table->entriesLength; j++) { encounter_entry = &(encounter_table->entries[j]); if (encounter_entry->counter != -1) { if (db_fwriteInt(stream, i) == -1) return -1; if (db_fwriteInt(stream, j) == -1) return -1; if (db_fwriteInt(stream, encounter_entry->counter) == -1) return -1; } } } return 0; } // 0x4BD28C int wmWorldMap_load(File* stream) { int i; int j; int k; int cities_count; int v38; int v39; int v35; EncounterTable* encounter_table; EncounterEntry* encounter_entry; if (fileReadBool(stream, &(wmGenData.didMeetFrankHorrigan)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.currentAreaId)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.worldPosX)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.worldPosY)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.encounterIconIsVisible)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.encounterMapId)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.encounterTableId)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.encounterEntryId)) == -1) return -1; if (fileReadBool(stream, &(wmGenData.isInCar)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.currentCarAreaId)) == -1) return -1; if (db_freadInt(stream, &(wmGenData.carFuel)) == -1) return -1; if (db_freadInt(stream, &(cities_count)) == -1) return -1; for (int cityIdx = 0; cityIdx < cities_count; cityIdx++) { CityInfo* city = &(wmAreaInfoList[cityIdx]); if (db_freadInt(stream, &(city->x)) == -1) return -1; if (db_freadInt(stream, &(city->y)) == -1) return -1; if (db_freadInt(stream, &(city->state)) == -1) return -1; if (db_freadInt(stream, &(city->visitedState)) == -1) return -1; int entranceCount; if (db_freadInt(stream, &(entranceCount)) == -1) { return -1; } for (int entranceIdx = 0; entranceIdx < entranceCount; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (db_freadInt(stream, &(entrance->state)) == -1) { return -1; } } } if (db_freadInt(stream, &(v39)) == -1) return -1; if (db_freadInt(stream, &(v38)) == -1) return -1; for (int tileIndex = 0; tileIndex < v39; tileIndex++) { TileInfo* tile = &(wmTileInfoList[tileIndex]); for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { SubtileInfo* subtile = &(tile->subtiles[column][row]); if (db_freadInt(stream, &(subtile->state)) == -1) return -1; } } } if (db_freadInt(stream, &(v35)) == -1) return -1; for (i = 0; i < v35; i++) { if (db_freadInt(stream, &(j)) == -1) return -1; encounter_table = &(wmEncounterTableList[j]); if (db_freadInt(stream, &(k)) == -1) return -1; encounter_entry = &(encounter_table->entries[k]); if (db_freadInt(stream, &(encounter_entry->counter)) == -1) return -1; } wmInterfaceCenterOnParty(); return 0; } // 0x4BD678 static int wmWorldMapSaveTempData() { File* stream = db_fopen("worldmap.dat", "wb"); if (stream == NULL) { return -1; } int rc = 0; if (wmWorldMap_save(stream) == -1) { rc = -1; } db_fclose(stream); return rc; } // 0x4BD6B4 static int wmWorldMapLoadTempData() { File* stream = db_fopen("worldmap.dat", "rb"); if (stream == NULL) { return -1; } int rc = 0; if (wmWorldMap_load(stream) == -1) { rc = -1; } db_fclose(stream); return rc; } // 0x4BD6F0 static int wmConfigInit() { if (wmAreaInit() == -1) { return -1; } Config config; if (!config_init(&config)) { return -1; } if (config_load(&config, "data\\worldmap.txt", true)) { for (int index = 0; index < ENCOUNTER_FREQUENCY_TYPE_COUNT; index++) { if (!config_get_value(&config, "data", wmFreqStrs[index], &(wmFreqValues[index]))) { break; } } char* terrainTypes; config_get_string(&config, "data", "terrain_types", &terrainTypes); wmParseTerrainTypes(&config, terrainTypes); for (int index = 0;; index++) { char section[40]; sprintf(section, "Encounter Table %d", index); char* lookupName; if (!config_get_string(&config, section, "lookup_name", &lookupName)) { break; } if (wmReadEncounterType(&config, lookupName, section) == -1) { return -1; } } if (!config_get_value(&config, "Tile Data", "num_horizontal_tiles", &wmNumHorizontalTiles)) { GNWSystemError("\nwmConfigInit::Error loading tile data!"); return -1; } for (int tileIndex = 0; tileIndex < 9999; tileIndex++) { char section[40]; sprintf(section, "Tile %d", tileIndex); int artIndex; if (!config_get_value(&config, section, "art_idx", &artIndex)) { break; } wmMaxTileNum++; TileInfo* worldmapTiles = (TileInfo*)mem_realloc(wmTileInfoList, sizeof(*wmTileInfoList) * wmMaxTileNum); if (worldmapTiles == NULL) { GNWSystemError("\nwmConfigInit::Error loading tiles!"); exit(1); } wmTileInfoList = worldmapTiles; TileInfo* tile = &(worldmapTiles[wmMaxTileNum - 1]); // NOTE: Uninline. wmTileSlotInit(tile); tile->fid = art_id(OBJ_TYPE_INTERFACE, artIndex, 0, 0, 0); int encounterDifficulty; if (config_get_value(&config, section, "encounter_difficulty", &encounterDifficulty)) { tile->encounterDifficultyModifier = encounterDifficulty; } char* walkMaskName; if (config_get_string(&config, section, "walk_mask_name", &walkMaskName)) { strncpy(tile->walkMaskName, walkMaskName, TILE_WALK_MASK_NAME_SIZE); } for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { char key[40]; sprintf(key, "%d_%d", row, column); char* subtileProps; if (!config_get_string(&config, section, key, &subtileProps)) { GNWSystemError("\nwmConfigInit::Error loading tiles!"); exit(1); } if (wmParseSubTileInfo(tile, row, column, subtileProps) == -1) { GNWSystemError("\nwmConfigInit::Error loading tiles!"); exit(1); } } } } } config_exit(&config); return 0; } // 0x4BD9F0 static int wmReadEncounterType(Config* config, char* lookupName, char* sectionKey) { wmMaxEncounterInfoTables++; EncounterTable* encounterTables = (EncounterTable*)mem_realloc(wmEncounterTableList, sizeof(EncounterTable) * wmMaxEncounterInfoTables); if (encounterTables == NULL) { GNWSystemError("\nwmConfigInit::Error loading Encounter Table!"); exit(1); } wmEncounterTableList = encounterTables; EncounterTable* encounterTable = &(encounterTables[wmMaxEncounterInfoTables - 1]); // NOTE: Uninline. wmEncounterTableSlotInit(encounterTable); encounterTable->field_28 = wmMaxEncounterInfoTables - 1; strncpy(encounterTable->lookupName, lookupName, 40); char* str; if (config_get_string(config, sectionKey, "maps", &str)) { while (*str != '\0') { if (encounterTable->mapsLength >= 6) { break; } if (strParseStrFromFunc(&str, &(encounterTable->maps[encounterTable->mapsLength]), wmParseFindMapIdxMatch) == -1) { break; } encounterTable->mapsLength++; } } for (;;) { char key[40]; sprintf(key, "enc_%02d", encounterTable->entriesLength); char* str; if (!config_get_string(config, sectionKey, key, &str)) { break; } if (encounterTable->entriesLength >= 40) { GNWSystemError("\nwmConfigInit::Error: Encounter Table: Too many table indexes!!"); exit(1); } pConfigCfg = config; if (wmParseEncounterTableIndex(&(encounterTable->entries[encounterTable->entriesLength]), str) == -1) { return -1; } encounterTable->entriesLength++; } return 0; } // 0x4BDB64 static int wmParseEncounterTableIndex(EncounterEntry* entry, char* string) { // NOTE: Uninline. if (wmEncounterTypeSlotInit(entry) == -1) { return -1; } while (string != NULL && *string != '\0') { strParseStrSepVal(&string, "chance", &(entry->chance), ":"); strParseStrSepVal(&string, "counter", &(entry->counter), ":"); if (strstr(string, "special")) { entry->flags |= ENCOUNTER_ENTRY_SPECIAL; string += 8; } if (string != NULL) { char* pch = strstr(string, "map:"); if (pch != NULL) { string = pch + 4; strParseStrFromFunc(&string, &(entry->map), wmParseFindMapIdxMatch); } } if (wmParseEncounterSubEncStr(entry, &string) == -1) { break; } if (string != NULL) { char* pch = strstr(string, "scenery:"); if (pch != NULL) { string = pch + 8; strParseStrFromList(&string, &(entry->scenery), wmSceneryStrs, ENCOUNTER_SCENERY_TYPE_COUNT); } } wmParseConditional(&string, "if", &(entry->condition)); } return 0; } // 0x4BDCA8 static int wmParseEncounterSubEncStr(EncounterEntry* encounterEntry, char** stringPtr) { char* string = *stringPtr; if (strnicmp(string, "enc:", 4) != 0) { return -1; } // Consume "enc:". string += 4; char* comma = strstr(string, ","); if (comma != NULL) { // Comma is present, position string pointer to the next chunk. *stringPtr = comma + 1; *comma = '\0'; } else { // No comma, this chunk is the last one. *stringPtr = NULL; } while (string != NULL) { ENCOUNTER_ENTRY_ENC* entry = &(encounterEntry->field_54[encounterEntry->field_50]); // NOTE: Uninline. wmEncounterSubEncSlotInit(entry); if (*string == '(') { string++; entry->minQuantity = atoi(string); while (*string != '\0' && *string != '-') { string++; } if (*string == '-') { string++; } entry->maxQuantity = atoi(string); while (*string != '\0' && *string != ')') { string++; } if (*string == ')') { string++; } } while (*string == ' ') { string++; } char* end = string; while (*end != '\0' && *end != ' ') { end++; } char ch = *end; *end = '\0'; if (strParseStrFromFunc(&string, &(entry->field_8), wmParseFindSubEncTypeMatch) == -1) { return -1; } *end = ch; if (ch == ' ') { string++; } end = string; while (*end != '\0' && *end != ' ') { end++; } ch = *end; *end = '\0'; if (*string != '\0') { strParseStrFromList(&string, &(entry->situation), wmEncOpStrs, ENCOUNTER_SITUATION_COUNT); } *end = ch; encounterEntry->field_50++; while (*string == ' ') { string++; } if (*string == '\0') { string = NULL; } } if (comma != NULL) { *comma = ','; } return 0; } // 0x4BDE94 static int wmParseFindSubEncTypeMatch(char* str, int* valuePtr) { *valuePtr = 0; if (stricmp(str, "player") == 0) { *valuePtr = -1; return 0; } if (wmFindEncBaseTypeMatch(str, valuePtr) == 0) { return 0; } if (wmReadEncBaseType(str, valuePtr) == 0) { return 0; } return -1; } // 0x4BDED8 static int wmFindEncBaseTypeMatch(char* str, int* valuePtr) { for (int index = 0; index < _wmMaxEncBaseTypes; index++) { if (stricmp(wmEncBaseTypeList[index].name, str) == 0) { *valuePtr = index; return 0; } } *valuePtr = -1; return -1; } // 0x4BDF34 static int wmReadEncBaseType(char* name, int* valuePtr) { char section[40]; sprintf(section, "Encounter: %s", name); char key[40]; sprintf(key, "type_00"); char* string; if (!config_get_string(pConfigCfg, section, key, &string)) { return -1; } _wmMaxEncBaseTypes++; ENC_BASE_TYPE* arr = (ENC_BASE_TYPE*)mem_realloc(wmEncBaseTypeList, sizeof(*wmEncBaseTypeList) * _wmMaxEncBaseTypes); if (arr == NULL) { GNWSystemError("\nwmConfigInit::Error Reading EncBaseType!"); exit(1); } wmEncBaseTypeList = arr; ENC_BASE_TYPE* entry = &(arr[_wmMaxEncBaseTypes - 1]); // NOTE: Uninline. wmEncBaseTypeSlotInit(entry); strncpy(entry->name, name, 40); while (1) { if (wmParseEncBaseSubTypeStr(&(entry->field_38[entry->field_34]), &string) == -1) { return -1; } entry->field_34++; sprintf(key, "type_%02d", entry->field_34); if (!config_get_string(pConfigCfg, section, key, &string)) { int team; config_get_value(pConfigCfg, section, "team_num", &team); for (int index = 0; index < entry->field_34; index++) { ENC_BASE_TYPE_38* ptr = &(entry->field_38[index]); if (PID_TYPE(ptr->pid) == OBJ_TYPE_CRITTER) { ptr->team = team; } } if (config_get_string(pConfigCfg, section, "position", &string)) { strParseStrFromList(&string, &(entry->position), wmFormationStrs, ENCOUNTER_FORMATION_TYPE_COUNT); strParseStrSepVal(&string, "spacing", &(entry->spacing), ":"); strParseStrSepVal(&string, "distance", &(entry->distance), ":"); } *valuePtr = _wmMaxEncBaseTypes - 1; return 0; } } return -1; } // 0x4BE140 static int wmParseEncBaseSubTypeStr(ENC_BASE_TYPE_38* ptr, char** stringPtr) { char* string = *stringPtr; // NOTE: Uninline. if (wmEncBaseSubTypeSlotInit(ptr) == -1) { return -1; } if (strParseStrSepVal(&string, "ratio", &(ptr->ratio), ":") == 0) { ptr->field_2C = 0; } if (strstr(string, "dead,") == string) { ptr->flags |= ENCOUNTER_SUBINFO_DEAD; string += 5; } strParseStrSepVal(&string, "pid", &(ptr->pid), ":"); if (ptr->pid == 0) { ptr->pid = -1; } strParseStrSepVal(&string, "distance", &(ptr->distance), ":"); strParseStrSepVal(&string, "tilenum", &(ptr->tile), ":"); for (int index = 0; index < 10; index++) { if (strstr(string, "item:") == NULL) { break; } wmParseEncounterItemType(&string, &(ptr->items[ptr->itemsLength]), &(ptr->itemsLength), ":"); } strParseStrSepVal(&string, "script", &(ptr->script), ":"); wmParseConditional(&string, "if", &(ptr->condition)); return 0; } // NOTE: Inlined. // // 0x4BE2A0 static int wmEncBaseTypeSlotInit(ENC_BASE_TYPE* entry) { entry->name[0] = '\0'; entry->position = ENCOUNTER_FORMATION_TYPE_SURROUNDING; entry->spacing = 1; entry->distance = -1; entry->field_34 = 0; return 0; } // NOTE: Inlined. // // 0x4BE2C4 static int wmEncBaseSubTypeSlotInit(ENC_BASE_TYPE_38* entry) { entry->field_28 = -1; entry->field_2C = 1; entry->ratio = 100; entry->pid = -1; entry->flags = 0; entry->distance = 0; entry->tile = -1; entry->itemsLength = 0; entry->script = -1; entry->team = -1; return wmConditionalDataInit(&(entry->condition)); } // NOTE: Inlined. // // 0x4BE32C static int wmEncounterSubEncSlotInit(ENCOUNTER_ENTRY_ENC* entry) { entry->minQuantity = 1; entry->maxQuantity = 1; entry->field_8 = -1; entry->situation = ENCOUNTER_SITUATION_NOTHING; return 0; } // NOTE: Inlined. // // 0x4BE34C static int wmEncounterTypeSlotInit(EncounterEntry* entry) { entry->flags = 0; entry->map = -1; entry->scenery = ENCOUNTER_SCENERY_TYPE_NORMAL; entry->chance = 0; entry->counter = -1; entry->field_50 = 0; return wmConditionalDataInit(&(entry->condition)); } // NOTE: Inlined. // // 0x4BE3B8 static int wmEncounterTableSlotInit(EncounterTable* encounterTable) { encounterTable->lookupName[0] = '\0'; encounterTable->mapsLength = 0; encounterTable->field_48 = 0; encounterTable->entriesLength = 0; return 0; } // NOTE: Inlined. // // 0x4BE3D4 static int wmTileSlotInit(TileInfo* tile) { tile->fid = -1; tile->handle = INVALID_CACHE_ENTRY; tile->data = NULL; tile->walkMaskName[0] = '\0'; tile->walkMaskData = NULL; tile->encounterDifficultyModifier = 0; return 0; } // NOTE: Inlined. // // 0x4BE400 static int wmTerrainTypeSlotInit(Terrain* terrain) { terrain->lookupName[0] = '\0'; terrain->type = 0; terrain->mapsLength = 0; return 0; } // 0x4BE378 static int wmConditionalDataInit(EncounterCondition* condition) { condition->entriesLength = 0; for (int index = 0; index < 3; index++) { EncounterConditionEntry* conditionEntry = &(condition->entries[index]); conditionEntry->type = ENCOUNTER_CONDITION_TYPE_NONE; conditionEntry->conditionalOperator = ENCOUNTER_CONDITIONAL_OPERATOR_NONE; conditionEntry->param = 0; conditionEntry->value = 0; } for (int index = 0; index < 2; index++) { condition->logicalOperators[index] = ENCOUNTER_LOGICAL_OPERATOR_NONE; } return 0; } // 0x4BE414 static int wmParseTerrainTypes(Config* config, char* string) { if (*string == '\0') { return -1; } int terrainCount = 1; char* pch = string; while (*pch != '\0') { if (*pch == ',') { terrainCount++; } pch++; } wmMaxTerrainTypes = terrainCount; wmTerrainTypeList = (Terrain*)mem_malloc(sizeof(*wmTerrainTypeList) * terrainCount); if (wmTerrainTypeList == NULL) { return -1; } for (int index = 0; index < wmMaxTerrainTypes; index++) { Terrain* terrain = &(wmTerrainTypeList[index]); // NOTE: Uninline. wmTerrainTypeSlotInit(terrain); } strlwr(string); pch = string; for (int index = 0; index < wmMaxTerrainTypes; index++) { Terrain* terrain = &(wmTerrainTypeList[index]); pch += strspn(pch, " "); int endPos = strcspn(pch, ","); char end = pch[endPos]; pch[endPos] = '\0'; int delimeterPos = strcspn(pch, ":"); char delimeter = pch[delimeterPos]; pch[delimeterPos] = '\0'; strncpy(terrain->lookupName, pch, 40); terrain->type = atoi(pch + delimeterPos + 1); pch[delimeterPos] = delimeter; pch[endPos] = end; if (end == ',') { pch += endPos + 1; } } for (int index = 0; index < wmMaxTerrainTypes; index++) { wmParseTerrainRndMaps(config, &(wmTerrainTypeList[index])); } return 0; } // 0x4BE598 static int wmParseTerrainRndMaps(Config* config, Terrain* terrain) { char section[40]; sprintf(section, "Random Maps: %s", terrain->lookupName); for (;;) { char key[40]; sprintf(key, "map_%02d", terrain->mapsLength); char* string; if (!config_get_string(config, section, key, &string)) { break; } if (strParseStrFromFunc(&string, &(terrain->maps[terrain->mapsLength]), wmParseFindMapIdxMatch) == -1) { return -1; } terrain->mapsLength++; if (terrain->mapsLength >= 20) { return -1; } } return 0; } // 0x4BE61C static int wmParseSubTileInfo(TileInfo* tile, int row, int column, char* string) { SubtileInfo* subtile = &(tile->subtiles[column][row]); subtile->state = SUBTILE_STATE_UNKNOWN; if (strParseStrFromFunc(&string, &(subtile->terrain), wmParseFindTerrainTypeMatch) == -1) { return -1; } if (strParseStrFromList(&string, &(subtile->field_4), wmFillStrs, 9) == -1) { return -1; } for (int index = 0; index < DAY_PART_COUNT; index++) { if (strParseStrFromList(&string, &(subtile->encounterChance[index]), wmFreqStrs, ENCOUNTER_FREQUENCY_TYPE_COUNT) == -1) { return -1; } } if (strParseStrFromFunc(&string, &(subtile->encounterType), wmParseFindEncounterTypeMatch) == -1) { return -1; } return 0; } // 0x4BE6D4 static int wmParseFindEncounterTypeMatch(char* string, int* valuePtr) { for (int index = 0; index < wmMaxEncounterInfoTables; index++) { if (stricmp(string, wmEncounterTableList[index].lookupName) == 0) { *valuePtr = index; return 0; } } debug_printf("WorldMap Error: Couldn't find match for Encounter Type!"); *valuePtr = -1; return -1; } // 0x4BE73C static int wmParseFindTerrainTypeMatch(char* string, int* valuePtr) { for (int index = 0; index < wmMaxTerrainTypes; index++) { Terrain* terrain = &(wmTerrainTypeList[index]); if (stricmp(string, terrain->lookupName) == 0) { *valuePtr = index; return 0; } } debug_printf("WorldMap Error: Couldn't find match for Terrain Type!"); *valuePtr = -1; return -1; } // 0x4BE7A4 static int wmParseEncounterItemType(char** stringPtr, ENC_BASE_TYPE_38_48* a2, int* a3, const char* delim) { char* string; int v2, v3; char tmp, tmp2; int v20; string = *stringPtr; v20 = 0; if (*string == '\0') { return -1; } strlwr(string); if (*string == ',') { string++; *stringPtr += 1; } string += strspn(string, " "); v2 = strcspn(string, ","); tmp = string[v2]; string[v2] = '\0'; v3 = strcspn(string, delim); tmp2 = string[v3]; string[v3] = '\0'; if (strcmp(string, "item") == 0) { *stringPtr += v2 + 1; v20 = 1; wmParseItemType(string + v3 + 1, a2); *a3 = *a3 + 1; } string[v3] = tmp2; string[v2] = tmp; return v20 ? 0 : -1; } // 0x4BE888 static int wmParseItemType(char* string, ENC_BASE_TYPE_38_48* ptr) { while (*string == ' ') { string++; } ptr->minimumQuantity = 1; ptr->maximumQuantity = 1; ptr->isEquipped = false; if (*string == '(') { string++; ptr->minimumQuantity = atoi(string); while (isdigit(*string)) { string++; } if (*string == '-') { string++; ptr->maximumQuantity = atoi(string); while (isdigit(*string)) { string++; } } else { ptr->maximumQuantity = ptr->minimumQuantity; } if (*string == ')') { string++; } } while (*string == ' ') { string++; } ptr->pid = atoi(string); while (isdigit(*string)) { string++; } while (*string == ' ') { string++; } if (strstr(string, "{wielded}") != NULL || strstr(string, "(wielded)") != NULL || strstr(string, "{worn}") != NULL || strstr(string, "(worn)") != NULL) { ptr->isEquipped = true; } return 0; } // 0x4BE988 static int wmParseConditional(char** stringPtr, const char* a2, EncounterCondition* condition) { while (condition->entriesLength < 3) { EncounterConditionEntry* conditionEntry = &(condition->entries[condition->entriesLength]); if (wmParseSubConditional(stringPtr, a2, &(conditionEntry->type), &(conditionEntry->conditionalOperator), &(conditionEntry->param), &(conditionEntry->value)) == -1) { return -1; } condition->entriesLength++; char* andStatement = strstr(*stringPtr, "and"); if (andStatement != NULL) { *stringPtr = andStatement + 3; condition->logicalOperators[condition->entriesLength - 1] = ENCOUNTER_LOGICAL_OPERATOR_AND; continue; } char* orStatement = strstr(*stringPtr, "or"); if (orStatement != NULL) { *stringPtr = orStatement + 2; condition->logicalOperators[condition->entriesLength - 1] = ENCOUNTER_LOGICAL_OPERATOR_OR; continue; } break; } return 0; } // 0x4BEA24 static int wmParseSubConditional(char** stringPtr, const char* a2, int* typePtr, int* operatorPtr, int* paramPtr, int* valuePtr) { char* pch; int v2; int v3; char tmp; char tmp2; int v57; char* string = *stringPtr; if (string == NULL) { return -1; } if (*string == '\0') { return -1; } strlwr(string); if (*string == ',') { string++; *stringPtr = string; } string += strspn(string, " "); v2 = strcspn(string, ","); tmp = *(string + v2); *(string + v2) = '\0'; v3 = strcspn(string, "("); tmp2 = *(string + v3); *(string + v3) = '\0'; v57 = 0; if (strstr(string, a2) == string) { v57 = 1; } *(string + v3) = tmp2; *(string + v2) = tmp; if (v57 == 0) { return -1; } string += v3 + 1; if (strstr(string, "rand(") == string) { string += 5; *typePtr = ENCOUNTER_CONDITION_TYPE_RANDOM; *operatorPtr = ENCOUNTER_CONDITIONAL_OPERATOR_NONE; *paramPtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } else if (strstr(string, "global(") == string) { string += 7; *typePtr = ENCOUNTER_CONDITION_TYPE_GLOBAL; *paramPtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "player(level)") == string) { string += 13; *typePtr = ENCOUNTER_CONDITION_TYPE_PLAYER; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "days_played") == string) { string += 11; *typePtr = ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "time_of_day") == string) { string += 11; *typePtr = ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else if (strstr(string, "enctr(num_critters)") == string) { string += 19; *typePtr = ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS; while (*string == ' ') { string++; } if (wmParseConditionalEval(&string, operatorPtr) != -1) { *valuePtr = atoi(string); pch = strstr(string, ")"); if (pch != NULL) { string = pch + 1; } pch = strstr(string, ","); if (pch != NULL) { string = pch + 1; } *stringPtr = string; return 0; } } else { *stringPtr = string; return 0; } return -1; } // 0x4BEEBC static int wmParseConditionalEval(char** stringPtr, int* conditionalOperatorPtr) { char* string = *stringPtr; *conditionalOperatorPtr = ENCOUNTER_CONDITIONAL_OPERATOR_NONE; int index; for (index = 0; index < ENCOUNTER_CONDITIONAL_OPERATOR_COUNT; index++) { if (strstr(string, wmConditionalOpStrs[index]) == string) { break; } } if (index == ENCOUNTER_CONDITIONAL_OPERATOR_COUNT) { return -1; } *conditionalOperatorPtr = index; string += strlen(wmConditionalOpStrs[index]); while (*string == ' ') { string++; } *stringPtr = string; return 0; } // NOTE: Inlined. // // 0x4BEF1C static int wmAreaSlotInit(CityInfo* area) { area->name[0] = '\0'; area->areaId = -1; area->x = 0; area->y = 0; area->size = CITY_SIZE_LARGE; area->state = CITY_STATE_UNKNOWN; area->lockState = LOCK_STATE_UNLOCKED; area->visitedState = 0; area->mapFid = -1; area->labelFid = -1; area->entrancesLength = 0; return 0; } // 0x4BEF68 static int wmAreaInit() { Config cfg; char section[40]; char key[40]; int area_idx; int num; char* str; CityInfo* cities; CityInfo* city; EntranceInfo* entrance; if (wmMapInit() == -1) { return -1; } if (!config_init(&cfg)) { return -1; } if (config_load(&cfg, "data\\city.txt", true)) { area_idx = 0; do { sprintf(section, "Area %02d", area_idx); if (!config_get_value(&cfg, section, "townmap_art_idx", &num)) { break; } wmMaxAreaNum++; cities = (CityInfo*)mem_realloc(wmAreaInfoList, sizeof(CityInfo) * wmMaxAreaNum); if (cities == NULL) { GNWSystemError("\nwmConfigInit::Error loading areas!"); exit(1); } wmAreaInfoList = cities; city = &(cities[wmMaxAreaNum - 1]); // NOTE: Uninline. wmAreaSlotInit(city); city->areaId = area_idx; if (num != -1) { num = art_id(OBJ_TYPE_INTERFACE, num, 0, 0, 0); } city->mapFid = num; if (config_get_value(&cfg, section, "townmap_label_art_idx", &num)) { if (num != -1) { num = art_id(OBJ_TYPE_INTERFACE, num, 0, 0, 0); } city->labelFid = num; } if (!config_get_string(&cfg, section, "area_name", &str)) { GNWSystemError("\nwmConfigInit::Error loading areas!"); exit(1); } strncpy(city->name, str, 40); if (!config_get_string(&cfg, section, "world_pos", &str)) { GNWSystemError("\nwmConfigInit::Error loading areas!"); exit(1); } if (strParseValue(&str, &(city->x)) == -1) { return -1; } if (strParseValue(&str, &(city->y)) == -1) { return -1; } if (!config_get_string(&cfg, section, "start_state", &str)) { GNWSystemError("\nwmConfigInit::Error loading areas!"); exit(1); } if (strParseStrFromList(&str, &(city->state), wmStateStrs, 2) == -1) { return -1; } if (config_get_string(&cfg, section, "lock_state", &str)) { if (strParseStrFromList(&str, &(city->lockState), wmStateStrs, 2) == -1) { return -1; } } if (!config_get_string(&cfg, section, "size", &str)) { GNWSystemError("\nwmConfigInit::Error loading areas!"); exit(1); } if (strParseStrFromList(&str, &(city->size), wmAreaSizeStrs, 3) == -1) { return -1; } while (city->entrancesLength < ENTRANCE_LIST_CAPACITY) { sprintf(key, "entrance_%d", city->entrancesLength); if (!config_get_string(&cfg, section, key, &str)) { break; } entrance = &(city->entrances[city->entrancesLength]); // NOTE: Uninline. wmEntranceSlotInit(entrance); if (strParseStrFromList(&str, &(entrance->state), wmStateStrs, 2) == -1) { return -1; } if (strParseValue(&str, &(entrance->x)) == -1) { return -1; } if (strParseValue(&str, &(entrance->y)) == -1) { return -1; } if (strParseStrFromFunc(&str, &(entrance->map), &wmParseFindMapIdxMatch) == -1) { return -1; } if (strParseValue(&str, &(entrance->elevation)) == -1) { return -1; } if (strParseValue(&str, &(entrance->tile)) == -1) { return -1; } if (strParseValue(&str, &(entrance->rotation)) == -1) { return -1; } city->entrancesLength++; } area_idx++; } while (area_idx < 5000); } config_exit(&cfg); if (wmMaxAreaNum != CITY_COUNT) { GNWSystemError("\nwmAreaInit::Error loading Cities!"); exit(1); } return 0; } // 0x4BF3E0 static int wmParseFindMapIdxMatch(char* string, int* valuePtr) { for (int index = 0; index < wmMaxMapNum; index++) { MapInfo* map = &(wmMapInfoList[index]); if (stricmp(string, map->lookupName) == 0) { *valuePtr = index; return 0; } } debug_printf("\nWorldMap Error: Couldn't find match for Map Index!"); *valuePtr = -1; return -1; } // NOTE: Inlined. // // 0x4BF448 static int wmEntranceSlotInit(EntranceInfo* entrance) { entrance->state = 0; entrance->x = 0; entrance->y = 0; entrance->map = -1; entrance->elevation = 0; entrance->tile = 0; entrance->rotation = 0; return 0; } // 0x4BF47C static int wmMapSlotInit(MapInfo* map) { map->lookupName[0] = '\0'; map->field_28 = -1; map->field_2C = -1; map->mapFileName[0] = '\0'; map->music[0] = '\0'; map->flags = 0x3F; map->ambientSoundEffectsLength = 0; map->startPointsLength = 0; return 0; } // 0x4BF4BC static int wmMapInit() { char* str; int num; MapInfo* maps; MapInfo* map; int j; MapAmbientSoundEffectInfo* sfx; MapStartPointInfo* rsp; Config config; if (!config_init(&config)) { return -1; } if (config_load(&config, "data\\maps.txt", true)) { for (int mapIdx = 0;; mapIdx++) { char section[40]; sprintf(section, "Map %03d", mapIdx); if (!config_get_string(&config, section, "lookup_name", &str)) { break; } wmMaxMapNum++; maps = (MapInfo*)mem_realloc(wmMapInfoList, sizeof(*wmMapInfoList) * wmMaxMapNum); if (maps == NULL) { GNWSystemError("\nwmConfigInit::Error loading maps!"); exit(1); } wmMapInfoList = maps; map = &(maps[wmMaxMapNum - 1]); wmMapSlotInit(map); strncpy(map->lookupName, str, 40); if (!config_get_string(&config, section, "map_name", &str)) { GNWSystemError("\nwmConfigInit::Error loading maps!"); exit(1); } strlwr(str); strncpy(map->mapFileName, str, 40); if (config_get_string(&config, section, "music", &str)) { strncpy(map->music, str, 40); } if (config_get_string(&config, section, "ambient_sfx", &str)) { while (str) { sfx = &(map->ambientSoundEffects[map->ambientSoundEffectsLength]); if (strParseStrAndSepVal(&str, sfx->name, &(sfx->chance), ":") == -1) { return -1; } map->ambientSoundEffectsLength++; if (*str == '\0') { str = NULL; } if (map->ambientSoundEffectsLength >= MAP_AMBIENT_SOUND_EFFECTS_CAPACITY) { if (str != NULL) { debug_printf("\nwmMapInit::Error reading ambient sfx. Too many! Str: %s, MapIdx: %d", map->lookupName, mapIdx); str = NULL; } } } } if (config_get_string(&config, section, "saved", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_SAVED, num); } if (config_get_string(&config, section, "dead_bodies_age", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_DEAD_BODIES_AGE, num); } if (config_get_string(&config, section, "can_rest_here", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_0, num); if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_1, num); if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_CAN_REST_ELEVATION_2, num); } if (config_get_string(&config, section, "pipbody_active", &str)) { if (strParseStrFromList(&str, &num, wmYesNoStrs, 2) == -1) { return -1; } // NOTE: Uninline. wmSetFlags(&(map->flags), MAP_PIPBOY_ACTIVE, num); } if (config_get_string(&config, section, "random_start_point_0", &str)) { j = 0; while (str != NULL) { while (*str != '\0') { if (map->startPointsLength >= MAP_STARTING_POINTS_CAPACITY) { break; } rsp = &(map->startPoints[map->startPointsLength]); // NOTE: Uninline. wmRStartSlotInit(rsp); strParseStrSepVal(&str, "elev", &(rsp->elevation), ":"); strParseStrSepVal(&str, "tile_num", &(rsp->tile), ":"); map->startPointsLength++; } char key[40]; sprintf(key, "random_start_point_%1d", ++j); if (!config_get_string(&config, section, key, &str)) { str = NULL; } } } } } config_exit(&config); return 0; } // NOTE: Inlined. // // 0x4BF954 static int wmRStartSlotInit(MapStartPointInfo* rsp) { rsp->elevation = 0; rsp->tile = -1; rsp->field_8 = -1; return 0; } // 0x4BF96C int wmMapMaxCount() { return wmMaxMapNum; } // 0x4BF974 int wmMapIdxToName(int mapIdx, char* dest) { if (mapIdx == -1 || mapIdx > wmMaxMapNum) { dest[0] = '\0'; return -1; } sprintf(dest, "%s.MAP", wmMapInfoList[mapIdx].mapFileName); return 0; } // 0x4BF9BC int wmMapMatchNameToIdx(char* name) { strlwr(name); char* pch = name; while (*pch != '\0' && *pch != '.') { pch++; } bool truncated = false; if (*pch != '\0') { *pch = '\0'; truncated = true; } int map = -1; for (int index = 0; index < wmMaxMapNum; index++) { if (strcmp(wmMapInfoList[index].mapFileName, name) == 0) { map = index; break; } } if (truncated) { *pch = '.'; } return map; } // 0x4BFA44 bool wmMapIdxIsSaveable(int mapIdx) { return (wmMapInfoList[mapIdx].flags & MAP_SAVED) != 0; } // 0x4BFA64 bool wmMapIsSaveable() { return (wmMapInfoList[map_data.field_34].flags & MAP_SAVED) != 0; } // 0x4BFA90 bool wmMapDeadBodiesAge() { return (wmMapInfoList[map_data.field_34].flags & MAP_DEAD_BODIES_AGE) != 0; } // 0x4BFABC bool wmMapCanRestHere(int elevation) { // 0x4BC860 static const int flags[ELEVATION_COUNT] = { MAP_CAN_REST_ELEVATION_0, MAP_CAN_REST_ELEVATION_1, MAP_CAN_REST_ELEVATION_2, }; MapInfo* map = &(wmMapInfoList[map_data.field_34]); return (map->flags & flags[elevation]) != 0; } // 0x4BFAFC bool wmMapPipboyActive() { return gmovie_has_been_played(MOVIE_VSUIT); } // 0x4BFB08 int wmMapMarkVisited(int mapIdx) { if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); if ((map->flags & MAP_SAVED) == 0) { return 0; } int areaIdx; if (wmMatchAreaContainingMapIdx(mapIdx, &areaIdx) == -1) { return -1; } // NOTE: Uninline. wmAreaMarkVisited(areaIdx); return 0; } // 0x4BFB64 static int wmMatchEntranceFromMap(int areaIdx, int mapIdx, int* entranceIdxPtr) { CityInfo* city = &(wmAreaInfoList[areaIdx]); for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (mapIdx == entrance->map) { *entranceIdxPtr = entranceIdx; return 0; } } *entranceIdxPtr = -1; return -1; } // 0x4BFBE8 static int wmMatchEntranceElevFromMap(int cityIdx, int map, int elevation, int* entranceIdxPtr) { CityInfo* city = &(wmAreaInfoList[cityIdx]); for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (entrance->map == map) { if (elevation == -1 || entrance->elevation == -1 || elevation == entrance->elevation) { *entranceIdxPtr = entranceIdx; return 0; } } } *entranceIdxPtr = -1; return -1; } // 0x4BFC7C static int wmMatchAreaFromMap(int mapIdx, int* cityIdxPtr) { for (int cityIdx = 0; cityIdx < wmMaxAreaNum; cityIdx++) { CityInfo* city = &(wmAreaInfoList[cityIdx]); for (int entranceIdx = 0; entranceIdx < city->entrancesLength; entranceIdx++) { EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (mapIdx == entrance->map) { *cityIdxPtr = cityIdx; return 0; } } } *cityIdxPtr = -1; return -1; } // Mark map entrance. // // 0x4BFD50 int wmMapMarkMapEntranceState(int mapIdx, int elevation, int state) { if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); if ((map->flags & MAP_SAVED) == 0) { return -1; } int cityIdx; if (wmMatchAreaContainingMapIdx(mapIdx, &cityIdx) == -1) { return -1; } int entranceIdx; if (wmMatchEntranceElevFromMap(cityIdx, mapIdx, elevation, &entranceIdx) == -1) { return -1; } CityInfo* city = &(wmAreaInfoList[cityIdx]); EntranceInfo* entrance = &(city->entrances[entranceIdx]); entrance->state = state; return 0; } // 0x4BFE0C void wmWorldMap() { wmWorldMapFunc(0); } // 0x4BFE10 static int wmWorldMapFunc(int a1) { if (wmInterfaceInit() == -1) { wmInterfaceExit(); return -1; } wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentAreaId)); unsigned int v24 = 0; int map = -1; int v25 = 0; int rc = 0; for (;;) { int keyCode = get_input(); unsigned int tick = get_time(); int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int v4 = wmWorldOffsetX + mouseX - WM_VIEW_X; int v5 = wmWorldOffsetY + mouseY - WM_VIEW_Y; if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } // NOTE: Uninline. wmCheckGameEvents(); if (game_user_wants_to_quit != 0) { break; } int mouseEvent = mouse_get_buttons(); if (wmGenData.isWalking) { wmPartyWalkingStep(); if (wmGenData.isInCar) { wmPartyWalkingStep(); wmPartyWalkingStep(); wmPartyWalkingStep(); if (game_get_global_var(GVAR_CAR_BLOWER)) { wmPartyWalkingStep(); } if (game_get_global_var(GVAR_NEW_RENO_CAR_UPGRADE)) { wmPartyWalkingStep(); } if (game_get_global_var(GVAR_NEW_RENO_SUPER_CAR)) { wmPartyWalkingStep(); wmPartyWalkingStep(); wmPartyWalkingStep(); } wmGenData.carImageCurrentFrameIndex++; if (wmGenData.carImageCurrentFrameIndex >= art_frame_max_frame(wmGenData.carImageFrm)) { wmGenData.carImageCurrentFrameIndex = 0; } wmCarUseGas(100); if (wmGenData.carFuel <= 0) { wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmMatchWorldPosToArea(v4, v5, &(wmGenData.currentAreaId)); wmGenData.isInCar = false; if (wmGenData.currentAreaId == -1) { wmGenData.currentCarAreaId = CITY_CAR_OUT_OF_GAS; CityInfo* city = &(wmAreaInfoList[CITY_CAR_OUT_OF_GAS]); CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); int worldmapX = wmGenData.worldPosX + wmGenData.hotspotFrmWidth / 2 + citySizeDescription->width / 2; int worldmapY = wmGenData.worldPosY + wmGenData.hotspotFrmHeight / 2 + citySizeDescription->height / 2; wmAreaSetWorldPos(CITY_CAR_OUT_OF_GAS, worldmapX, worldmapY); city->state = CITY_STATE_KNOWN; city->visitedState = 1; wmGenData.currentAreaId = CITY_CAR_OUT_OF_GAS; } else { wmGenData.currentCarAreaId = wmGenData.currentAreaId; } debug_printf("\nRan outta gas!"); } } wmInterfaceRefresh(); if (elapsed_tocks(tick, v24) > 1000) { if (partyMemberRestingHeal(3)) { intface_update_hit_points(false); v24 = tick; } } wmMarkSubTileRadiusVisited(wmGenData.worldPosX, wmGenData.worldPosY); if (wmGenData.walkDistance <= 0) { wmGenData.isWalking = false; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentAreaId)); } wmInterfaceRefresh(); if (wmGameTimeIncrement(18000)) { if (game_user_wants_to_quit != 0) { break; } } if (wmGenData.isWalking) { if (wmRndEncounterOccurred()) { if (wmGenData.encounterMapId != -1) { if (wmGenData.isInCar) { wmMatchAreaContainingMapIdx(wmGenData.encounterMapId, &(wmGenData.currentCarAreaId)); } map_load_idx(wmGenData.encounterMapId); } break; } } } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_LEFT_BUTTON_REPEAT) == 0) { if (mouse_click_in(WM_VIEW_X, WM_VIEW_Y, 472, 465)) { if (!wmGenData.isWalking && !wmGenData.mousePressed && abs(wmGenData.worldPosX - v4) < 5 && abs(wmGenData.worldPosY - v5) < 5) { wmGenData.mousePressed = true; wmInterfaceRefresh(); } } else { continue; } } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { if (wmGenData.mousePressed) { wmGenData.mousePressed = false; wmInterfaceRefresh(); if (abs(wmGenData.worldPosX - v4) < 5 && abs(wmGenData.worldPosY - v5) < 5) { if (wmGenData.currentAreaId != -1) { CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (city->visitedState == 2 && city->mapFid != -1) { if (wmTownMapFunc(&map) == -1) { v25 = -1; break; } } else { if (wmAreaFindFirstValidMap(&map) == -1) { v25 = -1; break; } city->visitedState = 2; } } else { map = 0; } if (map != -1) { if (wmGenData.isInCar) { wmGenData.isInCar = false; if (wmGenData.currentAreaId == -1) { wmMatchAreaContainingMapIdx(map, &(wmGenData.currentCarAreaId)); } else { wmGenData.currentCarAreaId = wmGenData.currentAreaId; } } map_load_idx(map); break; } } } else { if (mouse_click_in(WM_VIEW_X, WM_VIEW_Y, 472, 465)) { wmPartyInitWalking(v4, v5); } wmGenData.mousePressed = false; } } // NOTE: Uninline. wmInterfaceScrollTabsUpdate(); if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T) { if (!wmGenData.isWalking && wmGenData.currentAreaId != -1) { CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (city->visitedState == 2 && city->mapFid != -1) { if (wmTownMapFunc(&map) == -1) { rc = -1; } if (map != -1) { if (wmGenData.isInCar) { wmMatchAreaContainingMapIdx(map, &(wmGenData.currentCarAreaId)); } map_load_idx(map); } } } } else if (keyCode == KEY_HOME) { wmInterfaceCenterOnParty(); } else if (keyCode == KEY_ARROW_UP) { // NOTE: Uninline. wmInterfaceScroll(0, -1, NULL); } else if (keyCode == KEY_ARROW_LEFT) { // NOTE: Uninline. wmInterfaceScroll(-1, 0, NULL); } else if (keyCode == KEY_ARROW_DOWN) { // NOTE: Uninline. wmInterfaceScroll(0, 1, NULL); } else if (keyCode == KEY_ARROW_RIGHT) { // NOTE: Uninline. wmInterfaceScroll(1, 0, NULL); } else if (keyCode == KEY_CTRL_ARROW_UP) { wmInterfaceScrollTabsStart(-27); } else if (keyCode == KEY_CTRL_ARROW_DOWN) { wmInterfaceScrollTabsStart(27); } else if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) { int quickDestinationIndex = wmGenData.tabsOffsetY / 27 + (keyCode - KEY_CTRL_F1); if (quickDestinationIndex < wmLabelCount) { int cityIdx = wmLabelList[quickDestinationIndex]; CityInfo* city = &(wmAreaInfoList[cityIdx]); if (wmAreaIsKnown(city->areaId)) { if (wmGenData.currentAreaId != cityIdx) { wmPartyInitWalking(city->x, city->y); wmGenData.mousePressed = false; } } } } if (map != -1 || v25 == -1) { break; } } if (wmInterfaceExit() == -1) { return -1; } return rc; } // 0x4C056C int wmCheckGameAreaEvents() { if (wmGenData.currentAreaId == CITY_FAKE_VAULT_13_A) { if (wmGenData.currentAreaId < wmMaxAreaNum) { wmAreaInfoList[CITY_FAKE_VAULT_13_A].state = CITY_STATE_UNKNOWN; } if (wmMaxAreaNum > CITY_FAKE_VAULT_13_B) { wmAreaInfoList[CITY_FAKE_VAULT_13_B].state = CITY_STATE_KNOWN; } wmAreaMarkVisitedState(CITY_FAKE_VAULT_13_B, 2); } return 0; } // 0x4C05C4 static int wmInterfaceCenterOnParty() { int v0; int v1; v0 = wmGenData.worldPosX - 203; if ((v0 & 0x80000000) == 0) { if (v0 > wmGenData.viewportMaxX) { v0 = wmGenData.viewportMaxX; } } else { v0 = 0; } v1 = wmGenData.worldPosY - 200; if ((v1 & 0x80000000) == 0) { if (v1 > wmGenData.viewportMaxY) { v1 = wmGenData.viewportMaxY; } } else { v1 = 0; } wmWorldOffsetX = v0; wmWorldOffsetY = v1; wmInterfaceRefresh(); return 0; } // NOTE: Inlined. // // 0x4C0624 static void wmCheckGameEvents() { scriptsCheckGameEvents(NULL, wmBkWin); } // 0x4C0634 static int wmRndEncounterOccurred() { unsigned int v0 = get_time(); if (elapsed_tocks(v0, wmLastRndTime) < 1500) { return 0; } wmLastRndTime = v0; if (abs(wmGenData.oldWorldPosX - wmGenData.worldPosX) < 3) { return 0; } if (abs(wmGenData.oldWorldPosY - wmGenData.worldPosY) < 3) { return 0; } int v26; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosY, &v26); if (v26 != -1) { return 0; } if (!wmGenData.didMeetFrankHorrigan) { unsigned int gameTime = game_time(); if (gameTime / GAME_TIME_TICKS_PER_DAY > 35) { wmGenData.encounterMapId = v26; wmGenData.didMeetFrankHorrigan = true; if (wmGenData.isInCar) { wmMatchAreaContainingMapIdx(MAP_IN_GAME_MOVIE1, &(wmGenData.currentCarAreaId)); } map_load_idx(MAP_IN_GAME_MOVIE1); return 1; } } // NOTE: Uninline. wmPartyFindCurSubTile(); int dayPart; int gameTimeHour = game_time_hour(); if (gameTimeHour >= 1800 || gameTimeHour < 600) { dayPart = DAY_PART_NIGHT; } else if (gameTimeHour >= 1200) { dayPart = DAY_PART_AFTERNOON; } else { dayPart = DAY_PART_MORNING; } int frequency = wmFreqValues[wmGenData.currentSubtile->encounterChance[dayPart]]; if (frequency > 0 && frequency < 100) { int gameDifficulty = GAME_DIFFICULTY_NORMAL; if (config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) { int modifier = frequency / 15; switch (gameDifficulty) { case GAME_DIFFICULTY_EASY: frequency -= modifier; break; case GAME_DIFFICULTY_HARD: frequency += modifier; break; } } } int chance = roll_random(0, 100); if (chance >= frequency) { return 0; } wmRndEncounterPick(); int v8 = 1; wmGenData.encounterIconIsVisible = 1; wmGenData.encounterCursorId = 0; EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]); EncounterEntry* encounter = &(encounterTable->entries[wmGenData.encounterEntryId]); if ((encounter->flags & ENCOUNTER_ENTRY_SPECIAL) != 0) { wmGenData.encounterCursorId = 2; wmMatchAreaContainingMapIdx(wmGenData.encounterMapId, &v26); CityInfo* city = &(wmAreaInfoList[v26]); CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); int worldmapX = wmGenData.worldPosX + wmGenData.hotspotFrmWidth / 2 + citySizeDescription->width / 2; int worldmapY = wmGenData.worldPosY + wmGenData.hotspotFrmHeight / 2 + citySizeDescription->height / 2; wmAreaSetWorldPos(v26, worldmapX, worldmapY); v8 = 3; if (v26 >= 0 && v26 < wmMaxAreaNum) { CityInfo* city = &(wmAreaInfoList[v26]); if (city->lockState != LOCK_STATE_LOCKED) { city->state = CITY_STATE_KNOWN; } } } // Blinking. for (int index = 0; index < 7; index++) { wmGenData.encounterCursorId = v8 - wmGenData.encounterCursorId; if (wmInterfaceRefresh() == -1) { return -1; } block_for_tocks(200); } if (wmGenData.isInCar) { // 0x4BC86C static const int modifiers[DAY_PART_COUNT] = { 40, 30, 0, }; frequency -= modifiers[dayPart]; } bool randomEncounterIsDetected = false; if (frequency > chance) { int outdoorsman = partyMemberHighestSkillLevel(SKILL_OUTDOORSMAN); Object* scanner = inven_pid_is_carried(obj_dude, PROTO_ID_MOTION_SENSOR); if (scanner != NULL) { if (obj_dude == scanner->owner) { outdoorsman += 20; } } if (outdoorsman > 95) { outdoorsman = 95; } TileInfo* tile; // NOTE: Uninline. wmFindCurTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, &tile); debug_printf("\nEncounter Difficulty Mod: %d", tile->encounterDifficultyModifier); outdoorsman += tile->encounterDifficultyModifier; if (roll_random(1, 100) < outdoorsman) { randomEncounterIsDetected = true; int xp = 100 - outdoorsman; if (xp > 0) { MessageListItem messageListItem; char* text = getmsg(&misc_message_file, &messageListItem, 8500); if (strlen(text) < 110) { char formattedText[120]; sprintf(formattedText, text, xp); display_print(formattedText); } else { debug_printf("WorldMap: Error: Rnd Encounter string too long!"); } debug_printf("WorldMap: Giving Player [%d] Experience For Catching Rnd Encounter!", xp); if (xp < 100) { stat_pc_add_experience(xp); } } } } else { randomEncounterIsDetected = true; } wmGenData.oldWorldPosX = wmGenData.worldPosX; wmGenData.oldWorldPosY = wmGenData.worldPosY; if (randomEncounterIsDetected) { MessageListItem messageListItem; const char* title = gWorldmapEncDefaultMsg[0]; const char* body = gWorldmapEncDefaultMsg[1]; title = getmsg(&wmMsgFile, &messageListItem, 2999); body = getmsg(&wmMsgFile, &messageListItem, 3000 + 50 * wmGenData.encounterTableId + wmGenData.encounterEntryId); if (dialog_out(title, &body, 1, 169, 116, colorTable[32328], NULL, colorTable[32328], DIALOG_BOX_LARGE | DIALOG_BOX_YES_NO) == 0) { wmGenData.encounterIconIsVisible = 0; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; return 0; } } return 1; } // NOTE: Inlined. // // 0x4C0BE4 static int wmPartyFindCurSubTile() { return wmFindCurSubTileFromPos(wmGenData.worldPosX, wmGenData.worldPosY, &(wmGenData.currentSubtile)); } // 0x4C0C00 static int wmFindCurSubTileFromPos(int x, int y, SubtileInfo** subtilePtr) { int tileIndex = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles; TileInfo* tile = &(wmTileInfoList[tileIndex]); int column = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE; int row = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE; *subtilePtr = &(tile->subtiles[column][row]); return 0; } // NOTE: Inlined. // // 0x4C0CA8 static int wmFindCurTileFromPos(int x, int y, TileInfo** tilePtr) { int tileIndex = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles; *tilePtr = &(wmTileInfoList[tileIndex]); return 0; } // 0x4C0CF4 static int wmRndEncounterPick() { if (wmGenData.currentSubtile == NULL) { // NOTE: Uninline. wmPartyFindCurSubTile(); } wmGenData.encounterTableId = wmGenData.currentSubtile->encounterType; EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]); int candidates[41]; int candidatesLength = 0; int totalChance = 0; for (int index = 0; index < encounterTable->entriesLength; index++) { EncounterEntry* encounterTableEntry = &(encounterTable->entries[index]); bool selected = true; if (wmEvalConditional(&(encounterTableEntry->condition), NULL) == 0) { selected = false; } if (encounterTableEntry->counter == 0) { selected = false; } if (selected) { candidates[candidatesLength++] = index; totalChance += encounterTableEntry->chance; } } int v1 = critterGetStat(obj_dude, STAT_LUCK) - 5; int v2 = roll_random(0, totalChance) + v1; if (perkHasRank(obj_dude, PERK_EXPLORER)) { v2 += 2; } if (perkHasRank(obj_dude, PERK_RANGER)) { v2++; } if (perkHasRank(obj_dude, PERK_SCOUT)) { v2++; } int gameDifficulty; if (config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) { switch (gameDifficulty) { case GAME_DIFFICULTY_EASY: v2 += 5; if (v2 > totalChance) { v2 = totalChance; } break; case GAME_DIFFICULTY_HARD: v2 -= 5; if (v2 < 0) { v2 = 0; } break; } } int index; for (index = 0; index < candidatesLength; index++) { EncounterEntry* encounterTableEntry = &(encounterTable->entries[candidates[index]]); if (v2 < encounterTableEntry->chance) { break; } v2 -= encounterTableEntry->chance; } if (index == candidatesLength) { index = candidatesLength - 1; } wmGenData.encounterEntryId = candidates[index]; EncounterEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]); if (encounterTableEntry->counter > 0) { encounterTableEntry->counter--; } if (encounterTableEntry->map == -1) { if (encounterTable->mapsLength <= 0) { Terrain* terrain = &(wmTerrainTypeList[wmGenData.currentSubtile->terrain]); int randommapIdx = roll_random(0, terrain->mapsLength - 1); wmGenData.encounterMapId = terrain->maps[randommapIdx]; } else { int randommapIdx = roll_random(0, encounterTable->mapsLength - 1); wmGenData.encounterMapId = encounterTable->maps[randommapIdx]; } } else { wmGenData.encounterMapId = encounterTableEntry->map; } return 0; } // 0x4C0FA4 int wmSetupRandomEncounter() { MessageListItem messageListItem; char* msg; if (wmGenData.encounterMapId == -1) { return 0; } EncounterTable* encounterTable = &(wmEncounterTableList[wmGenData.encounterTableId]); EncounterEntry* encounterTableEntry = &(encounterTable->entries[wmGenData.encounterEntryId]); // You encounter: msg = getmsg(&wmMsgFile, &messageListItem, 2998); display_print(msg); msg = getmsg(&wmMsgFile, &messageListItem, 3000 + 50 * wmGenData.encounterTableId + wmGenData.encounterEntryId); display_print(msg); int gameDifficulty; switch (encounterTableEntry->scenery) { case ENCOUNTER_SCENERY_TYPE_NONE: case ENCOUNTER_SCENERY_TYPE_LIGHT: case ENCOUNTER_SCENERY_TYPE_NORMAL: case ENCOUNTER_SCENERY_TYPE_HEAVY: debug_printf("\nwmSetupRandomEncounter: Scenery Type: %s", wmSceneryStrs[encounterTableEntry->scenery]); config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty); break; default: debug_printf("\nERROR: wmSetupRandomEncounter: invalid Scenery Type!"); return -1; } Object* v0 = NULL; for (int i = 0; i < encounterTableEntry->field_50; i++) { ENCOUNTER_ENTRY_ENC* v3 = &(encounterTableEntry->field_54[i]); int v9 = roll_random(v3->minQuantity, v3->maxQuantity); switch (gameDifficulty) { case GAME_DIFFICULTY_EASY: v9 -= 2; if (v9 < v3->minQuantity) { v9 = v3->minQuantity; } break; case GAME_DIFFICULTY_HARD: v9 += 2; break; } int partyMemberCount = getPartyMemberCount(); if (partyMemberCount > 2) { v9 += 2; } if (v9 != 0) { Object* v35; if (wmSetupCritterObjs(v3->field_8, &v35, v9) == -1) { scripts_request_worldmap(); return -1; } if (i > 0) { if (v0 != NULL) { if (v0 != v35) { if (encounterTableEntry->field_50 != 1) { if (encounterTableEntry->field_50 == 2 && !isInCombat()) { v0->data.critter.combat.whoHitMe = v35; v35->data.critter.combat.whoHitMe = v0; STRUCT_664980 combat; combat.attacker = v0; combat.defender = v35; combat.actionPointsBonus = 0; combat.accuracyBonus = 0; combat.damageBonus = 0; combat.minDamage = 0; combat.maxDamage = 500; combat.field_1C = 0; caiSetupTeamCombat(v35, v0); scripts_request_combat_locked(&combat); } } else { if (!isInCombat()) { v0->data.critter.combat.whoHitMe = obj_dude; STRUCT_664980 combat; combat.attacker = v0; combat.defender = obj_dude; combat.actionPointsBonus = 0; combat.accuracyBonus = 0; combat.damageBonus = 0; combat.minDamage = 0; combat.maxDamage = 500; combat.field_1C = 0; caiSetupTeamCombat(obj_dude, v0); scripts_request_combat_locked(&combat); } } } } } v0 = v35; } } return 0; } // wmSetupCritterObjs // 0x4C11FC static int wmSetupCritterObjs(int type_idx, Object** critterPtr, int critterCount) { if (type_idx == -1) { return 0; } *critterPtr = 0; ENC_BASE_TYPE* v25 = &(wmEncBaseTypeList[type_idx]); debug_printf("\nwmSetupCritterObjs: typeIdx: %d, Formation: %s", type_idx, wmFormationStrs[v25->position]); if (wmSetupRndNextTileNumInit(v25) == -1) { return -1; } for (int i = 0; i < v25->field_34; i++) { ENC_BASE_TYPE_38* v5 = &(v25->field_38[i]); if (v5->pid == -1) { continue; } if (!wmEvalConditional(&(v5->condition), &critterCount)) { continue; } int v23; switch (v5->field_2C) { case 0: v23 = v5->ratio * critterCount / 100; break; case 1: v23 = 1; break; default: assert(false && "Should be unreachable"); } if (v23 < 1) { v23 = 1; } for (int j = 0; j < v23; j++) { int tile; if (wmSetupRndNextTileNum(v25, v5, &tile) == -1) { debug_printf("\nERROR: wmSetupCritterObjs: wmSetupRndNextTileNum:"); continue; } if (v5->pid == -1) { continue; } Object* object; if (obj_pid_new(&object, v5->pid) == -1) { return -1; } if (*critterPtr == NULL) { if (PID_TYPE(v5->pid) == OBJ_TYPE_CRITTER) { *critterPtr = object; } } if (v5->team != -1) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { object->data.critter.combat.team = v5->team; } } if (v5->script != -1) { if (object->sid != -1) { scr_remove(object->sid); object->sid = -1; } obj_new_sid_inst(object, SCRIPT_TYPE_CRITTER, v5->script - 1); } if (v25->position != ENCOUNTER_FORMATION_TYPE_SURROUNDING) { obj_move_to_tile(object, tile, map_elevation, NULL); } else { obj_attempt_placement(object, tile, 0, 0); } int direction = tile_dir(tile, obj_dude->tile); obj_set_rotation(object, direction, NULL); for (int itemIndex = 0; itemIndex < v5->itemsLength; itemIndex++) { ENC_BASE_TYPE_38_48* v10 = &(v5->items[itemIndex]); int quantity; if (v10->maximumQuantity == v10->minimumQuantity) { quantity = v10->maximumQuantity; } else { quantity = roll_random(v10->minimumQuantity, v10->maximumQuantity); } if (quantity == 0) { continue; } Object* item; if (obj_pid_new(&item, v10->pid) == -1) { return -1; } if (v10->pid == PROTO_ID_MONEY) { if (perkHasRank(obj_dude, PERK_FORTUNE_FINDER)) { quantity *= 2; } } if (item_add_force(object, item, quantity) == -1) { return -1; } obj_disconnect(item, NULL); if (v10->isEquipped) { if (inven_wield(object, item, 1) == -1) { debug_printf("\nERROR: wmSetupCritterObjs: Inven Wield Failed: %d on %s: Critter Fid: %d", item->pid, critter_name(object), object->fid); } } } } } return 0; } // 0x4C155C static int wmSetupRndNextTileNumInit(ENC_BASE_TYPE* a1) { for (int index = 0; index < 2; index++) { wmRndCenterRotations[index] = 0; wmRndTileDirs[index] = 0; wmRndCenterTiles[index] = -1; if (index & 1) { wmRndRotOffsets[index] = 5; } else { wmRndRotOffsets[index] = 1; } } wmRndCallCount = 0; switch (a1->position) { case ENCOUNTER_FORMATION_TYPE_SURROUNDING: wmRndCenterTiles[0] = obj_dude->tile; wmRndTileDirs[0] = roll_random(0, ROTATION_COUNT - 1); wmRndOriginalCenterTile = wmRndCenterTiles[0]; return 0; case ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE: case ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE: case ENCOUNTER_FORMATION_TYPE_WEDGE: case ENCOUNTER_FORMATION_TYPE_CONE: case ENCOUNTER_FORMATION_TYPE_HUDDLE: if (1) { MapInfo* map = &(wmMapInfoList[map_data.field_34]); if (map->startPointsLength != 0) { int rspIndex = roll_random(0, map->startPointsLength - 1); MapStartPointInfo* rsp = &(map->startPoints[rspIndex]); wmRndCenterTiles[0] = rsp->tile; wmRndCenterTiles[1] = wmRndCenterTiles[0]; wmRndCenterRotations[0] = rsp->field_8; wmRndCenterRotations[1] = wmRndCenterRotations[0]; } else { wmRndCenterRotations[0] = 0; wmRndCenterRotations[1] = 0; wmRndCenterTiles[0] = obj_dude->tile; wmRndCenterTiles[1] = obj_dude->tile; } wmRndTileDirs[0] = tile_dir(wmRndCenterTiles[0], obj_dude->tile); wmRndTileDirs[1] = tile_dir(wmRndCenterTiles[1], obj_dude->tile); wmRndOriginalCenterTile = wmRndCenterTiles[0]; return 0; } default: debug_printf("\nERROR: wmSetupCritterObjs: invalid Formation Type!"); return -1; } } // wmSetupRndNextTileNum // 0x4C16F0 static int wmSetupRndNextTileNum(ENC_BASE_TYPE* a1, ENC_BASE_TYPE_38* a2, int* out_tile_num) { int tile_num; int attempt = 0; while (1) { switch (a1->position) { case ENCOUNTER_FORMATION_TYPE_SURROUNDING: if (1) { int distance; if (a2->distance != 0) { distance = a2->distance; } else { distance = roll_random(-2, 2); distance += critterGetStat(obj_dude, STAT_PERCEPTION); if (perkHasRank(obj_dude, PERK_CAUTIOUS_NATURE)) { distance += 3; } } if (distance < 0) { distance = 0; } int origin = a2->tile; if (origin == -1) { origin = tile_num_in_direction(obj_dude->tile, wmRndTileDirs[0], distance); } if (++wmRndTileDirs[0] >= ROTATION_COUNT) { wmRndTileDirs[0] = 0; } int randomizedDistance = roll_random(0, distance / 2); int randomizedRotation = roll_random(0, ROTATION_COUNT - 1); tile_num = tile_num_in_direction(origin, (randomizedRotation + wmRndTileDirs[0]) % ROTATION_COUNT, randomizedDistance); } break; case ENCOUNTER_FORMATION_TYPE_STRAIGHT_LINE: tile_num = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { int rotation = (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT; int origin = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], rotation, a1->spacing); int v13 = tile_num_in_direction(origin, (rotation + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, a1->spacing); wmRndCenterTiles[wmRndIndex] = v13; wmRndIndex = 1 - wmRndIndex; tile_num = v13; } break; case ENCOUNTER_FORMATION_TYPE_DOUBLE_LINE: tile_num = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { int rotation = (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT; int origin = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], rotation, a1->spacing); int v17 = tile_num_in_direction(origin, (rotation + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, a1->spacing); wmRndCenterTiles[wmRndIndex] = v17; wmRndIndex = 1 - wmRndIndex; tile_num = v17; } break; case ENCOUNTER_FORMATION_TYPE_WEDGE: tile_num = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { tile_num = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], (wmRndRotOffsets[wmRndIndex] + wmRndTileDirs[wmRndIndex]) % ROTATION_COUNT, a1->spacing); wmRndCenterTiles[wmRndIndex] = tile_num; wmRndIndex = 1 - wmRndIndex; } break; case ENCOUNTER_FORMATION_TYPE_CONE: tile_num = wmRndCenterTiles[wmRndIndex]; if (wmRndCallCount != 0) { tile_num = tile_num_in_direction(wmRndCenterTiles[wmRndIndex], (wmRndTileDirs[wmRndIndex] + 3 + wmRndRotOffsets[wmRndIndex]) % ROTATION_COUNT, a1->spacing); wmRndCenterTiles[wmRndIndex] = tile_num; wmRndIndex = 1 - wmRndIndex; } break; case ENCOUNTER_FORMATION_TYPE_HUDDLE: tile_num = wmRndCenterTiles[0]; if (wmRndCallCount != 0) { wmRndTileDirs[0] = (wmRndTileDirs[0] + 1) % ROTATION_COUNT; tile_num = tile_num_in_direction(wmRndCenterTiles[0], wmRndTileDirs[0], a1->spacing); wmRndCenterTiles[0] = tile_num; } break; default: assert(false && "Should be unreachable"); } ++attempt; ++wmRndCallCount; if (wmEvalTileNumForPlacement(tile_num)) { break; } debug_printf("\nWARNING: EVAL-TILE-NUM FAILED!"); if (tile_dist(wmRndOriginalCenterTile, wmRndCenterTiles[wmRndIndex]) > 25) { return -1; } if (attempt > 25) { return -1; } } debug_printf("\nwmSetupRndNextTileNum:TileNum: %d", tile_num); *out_tile_num = tile_num; return 0; } // 0x4C1A64 bool wmEvalTileNumForPlacement(int tile) { if (obj_blocking_at(obj_dude, tile, map_elevation) != NULL) { return false; } if (make_path_func(obj_dude, obj_dude->tile, tile, NULL, 0, obj_shoot_blocking_at) == 0) { return false; } return true; } // 0x4C1AC8 static bool wmEvalConditional(EncounterCondition* a1, int* a2) { int value; bool matches = true; for (int index = 0; index < a1->entriesLength; index++) { EncounterConditionEntry* ptr = &(a1->entries[index]); matches = true; switch (ptr->type) { case ENCOUNTER_CONDITION_TYPE_GLOBAL: value = game_get_global_var(ptr->param); if (!wmEvalSubConditional(value, ptr->conditionalOperator, ptr->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_NUMBER_OF_CRITTERS: if (!wmEvalSubConditional(*a2, ptr->conditionalOperator, ptr->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_RANDOM: value = roll_random(0, 100); if (value > ptr->param) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_PLAYER: value = stat_pc_get(PC_STAT_LEVEL); if (!wmEvalSubConditional(value, ptr->conditionalOperator, ptr->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_DAYS_PLAYED: value = game_time(); if (!wmEvalSubConditional(value / GAME_TIME_TICKS_PER_DAY, ptr->conditionalOperator, ptr->value)) { matches = false; } break; case ENCOUNTER_CONDITION_TYPE_TIME_OF_DAY: value = game_time_hour(); if (!wmEvalSubConditional(value / 100, ptr->conditionalOperator, ptr->value)) { matches = false; } break; } if (!matches) { // FIXME: Can overflow with all 3 conditions specified. if (a1->logicalOperators[index] == ENCOUNTER_LOGICAL_OPERATOR_AND) { break; } } } return matches; } // 0x4C1C0C static bool wmEvalSubConditional(int operand1, int condionalOperator, int operand2) { switch (condionalOperator) { case ENCOUNTER_CONDITIONAL_OPERATOR_EQUAL: return operand1 == operand2; case ENCOUNTER_CONDITIONAL_OPERATOR_NOT_EQUAL: return operand1 != operand2; case ENCOUNTER_CONDITIONAL_OPERATOR_LESS_THAN: return operand1 < operand2; case ENCOUNTER_CONDITIONAL_OPERATOR_GREATER_THAN: return operand1 > operand2; } return false; } // 0x4C1C50 static bool wmGameTimeIncrement(int a1) { if (a1 == 0) { return false; } while (a1 != 0) { unsigned int gameTime = game_time(); unsigned int nextEventTime = queue_next_time(); int v1 = nextEventTime >= gameTime ? a1 : nextEventTime - gameTime; a1 -= v1; inc_game_time(v1); // NOTE: Uninline. wmInterfaceDialSyncTime(true); wmInterfaceRefreshDate(true); if (queue_process()) { break; } } return true; } // Reads .msk file if needed. // // 0x4C1CE8 static int wmGrabTileWalkMask(int tileIdx) { TileInfo* tileInfo = &(wmTileInfoList[tileIdx]); if (tileInfo->walkMaskData != NULL) { return 0; } if (*tileInfo->walkMaskName == '\0') { return 0; } tileInfo->walkMaskData = (unsigned char*)mem_malloc(13200); if (tileInfo->walkMaskData == NULL) { return -1; } char path[MAX_PATH]; sprintf(path, "data\\%s.msk", tileInfo->walkMaskName); File* stream = db_fopen(path, "rb"); if (stream == NULL) { return -1; } int rc = 0; if (db_freadByteCount(stream, tileInfo->walkMaskData, 13200) == -1) { rc = -1; } db_fclose(stream); return rc; } // 0x4C1D9C static bool wmWorldPosInvalid(int x, int y) { int tileIdx = y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles; if (wmGrabTileWalkMask(tileIdx) == -1) { return false; } TileInfo* tileDescription = &(wmTileInfoList[tileIdx]); unsigned char* mask = tileDescription->walkMaskData; if (mask == NULL) { return false; } // Mask length is 13200, which is 300 * 44 // 44 * 8 is 352, which is probably left 2 bytes intact // TODO: Check math. int pos = (y % WM_TILE_HEIGHT) * 44 + (x % WM_TILE_WIDTH) / 8; int bit = 1 << (((x % WM_TILE_WIDTH) / 8) & 3); return (mask[pos] & bit) != 0; } // 0x4C1E54 static void wmPartyInitWalking(int x, int y) { wmGenData.walkDestinationX = x; wmGenData.walkDestinationY = y; wmGenData.currentAreaId = -1; wmGenData.isWalking = true; int dx = abs(x - wmGenData.worldPosX); int dy = abs(y - wmGenData.worldPosY); if (dx < dy) { wmGenData.walkDistance = dy; wmGenData.walkLineDeltaMainAxisStep = 2 * dx; wmGenData.walkWorldPosMainAxisStepX = 0; wmGenData.walkLineDelta = 2 * dx - dy; wmGenData.walkLineDeltaCrossAxisStep = 2 * (dx - dy); wmGenData.walkWorldPosCrossAxisStepX = 1; wmGenData.walkWorldPosMainAxisStepY = 1; wmGenData.walkWorldPosCrossAxisStepY = 1; } else { wmGenData.walkDistance = dx; wmGenData.walkLineDeltaMainAxisStep = 2 * dy; wmGenData.walkWorldPosMainAxisStepY = 0; wmGenData.walkLineDelta = 2 * dy - dx; wmGenData.walkLineDeltaCrossAxisStep = 2 * (dy - dx); wmGenData.walkWorldPosMainAxisStepX = 1; wmGenData.walkWorldPosCrossAxisStepX = 1; wmGenData.walkWorldPosCrossAxisStepY = 1; } if (wmGenData.walkDestinationX < wmGenData.worldPosX) { wmGenData.walkWorldPosCrossAxisStepX = -wmGenData.walkWorldPosCrossAxisStepX; wmGenData.walkWorldPosMainAxisStepX = -wmGenData.walkWorldPosMainAxisStepX; } if (wmGenData.walkDestinationY < wmGenData.worldPosY) { wmGenData.walkWorldPosCrossAxisStepY = -wmGenData.walkWorldPosCrossAxisStepY; wmGenData.walkWorldPosMainAxisStepY = -wmGenData.walkWorldPosMainAxisStepY; } if (!wmCursorIsVisible()) { wmInterfaceCenterOnParty(); } } // 0x4C1F90 static void wmPartyWalkingStep() { // 0x51DEAC static int terrainCounter = 1; if (wmGenData.walkDistance <= 0) { return; } terrainCounter++; if (terrainCounter > 4) { terrainCounter = 1; } // NOTE: Uninline. wmPartyFindCurSubTile(); Terrain* terrain = &(wmTerrainTypeList[wmGenData.currentSubtile->terrain]); int v1 = terrain->type - perk_level(obj_dude, PERK_PATHFINDER); if (v1 < 1) { v1 = 1; } if (terrainCounter / v1 >= 1) { int v3; int v4; if (wmGenData.walkLineDelta >= 0) { if (wmWorldPosInvalid(wmGenData.walkWorldPosCrossAxisStepX + wmGenData.worldPosX, wmGenData.walkWorldPosCrossAxisStepY + wmGenData.worldPosY)) { wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosX, &(wmGenData.currentAreaId)); wmGenData.walkDistance = 0; return; } v3 = wmGenData.walkWorldPosCrossAxisStepX; wmGenData.walkLineDelta += wmGenData.walkLineDeltaCrossAxisStep; wmGenData.worldPosX += wmGenData.walkWorldPosCrossAxisStepX; v4 = wmGenData.walkWorldPosCrossAxisStepY; wmGenData.worldPosY += wmGenData.walkWorldPosCrossAxisStepY; } else { if (wmWorldPosInvalid(wmGenData.walkWorldPosMainAxisStepX + wmGenData.worldPosX, wmGenData.walkWorldPosMainAxisStepY + wmGenData.worldPosY) == 1) { wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmMatchWorldPosToArea(wmGenData.worldPosX, wmGenData.worldPosX, &(wmGenData.currentAreaId)); wmGenData.walkDistance = 0; return; } v3 = wmGenData.walkWorldPosMainAxisStepX; wmGenData.walkLineDelta += wmGenData.walkLineDeltaMainAxisStep; wmGenData.worldPosY += wmGenData.walkWorldPosMainAxisStepY; v4 = wmGenData.walkWorldPosMainAxisStepY; wmGenData.worldPosX += wmGenData.walkWorldPosMainAxisStepX; } wmInterfaceScrollPixel(1, 1, v3, v4, NULL, false); wmGenData.walkDistance -= 1; if (wmGenData.walkDistance == 0) { wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; wmGenData.walkDestinationX = 0; } } } // 0x4C219C static void wmInterfaceScrollTabsStart(int delta) { int i; int v3; for (i = 0; i < 7; i++) { win_disable_button(wmTownMapSubButtonIds[i]); } wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY; v3 = wmGenData.tabsOffsetY + 7 * delta; if (delta >= 0) { if (wmGenData.tabsBackgroundFrmHeight - 230 <= wmGenData.oldTabsOffsetY) { goto L11; } else { wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY + 7 * delta; if (v3 > wmGenData.tabsBackgroundFrmHeight - 230) { } } } else { if (wmGenData.tabsOffsetY <= 0) { goto L11; } else { wmGenData.oldTabsOffsetY = wmGenData.tabsOffsetY + 7 * delta; if (v3 < 0) { wmGenData.oldTabsOffsetY = 0; } } } wmGenData.tabsScrollingDelta = delta; L11: // NOTE: Uninline. wmInterfaceScrollTabsUpdate(); } // 0x4C2270 static void wmInterfaceScrollTabsStop() { int i; wmGenData.tabsScrollingDelta = 0; for (i = 0; i < 7; i++) { win_enable_button(wmTownMapSubButtonIds[i]); } } // NOTE: Inlined. // // 0x4C2290 static void wmInterfaceScrollTabsUpdate() { if (wmGenData.tabsScrollingDelta != 0) { wmGenData.tabsOffsetY += wmGenData.tabsScrollingDelta; wmRefreshInterfaceOverlay(1); if (wmGenData.tabsScrollingDelta >= 0) { if (wmGenData.oldTabsOffsetY <= wmGenData.tabsOffsetY) { // NOTE: Uninline. wmInterfaceScrollTabsStop(); } } else { if (wmGenData.oldTabsOffsetY >= wmGenData.tabsOffsetY) { // NOTE: Uninline. wmInterfaceScrollTabsStop(); } } } } // 0x4C2324 static int wmInterfaceInit() { int fid; Art* frm; CacheEntry* frmHandle; wmLastRndTime = get_time(); wmGenData.oldFont = text_curr(); text_font(0); map_save_in_game(true); const char* backgroundSoundFileName = wmGenData.isInCar ? "20car" : "23world"; gsound_background_play_level_music(backgroundSoundFileName, 12); disable_box_bar_win(); map_disable_bk_processes(); cycle_disable(); gmouse_set_cursor(MOUSE_CURSOR_ARROW); int worldmapWindowX = 0; int worldmapWindowY = 0; wmBkWin = win_add(worldmapWindowX, worldmapWindowY, WM_WINDOW_WIDTH, WM_WINDOW_HEIGHT, colorTable[0], WINDOW_FLAG_0x04); if (wmBkWin == -1) { return -1; } fid = art_id(OBJ_TYPE_INTERFACE, 136, 0, 0, 0); frm = art_ptr_lock(fid, &wmBkKey); if (frm == NULL) { return -1; } wmBkWidth = art_frame_width(frm, 0, 0); wmBkHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(wmBkKey); wmBkKey = INVALID_CACHE_ENTRY; fid = art_id(OBJ_TYPE_INTERFACE, 136, 0, 0, 0); wmBkArtBuf = art_ptr_lock_data(fid, 0, 0, &wmBkKey); if (wmBkArtBuf == NULL) { return -1; } wmBkWinBuf = win_get_buf(wmBkWin); if (wmBkWinBuf == NULL) { return -1; } buf_to_buf(wmBkArtBuf, wmBkWidth, wmBkHeight, wmBkWidth, wmBkWinBuf, WM_WINDOW_WIDTH); for (int citySize = 0; citySize < CITY_SIZE_COUNT; citySize++) { CitySizeDescription* citySizeDescription = &(wmSphereData[citySize]); fid = art_id(OBJ_TYPE_INTERFACE, 336 + citySize, 0, 0, 0); citySizeDescription->fid = fid; frm = art_ptr_lock(fid, &(citySizeDescription->handle)); if (frm == NULL) { return -1; } citySizeDescription->width = art_frame_width(frm, 0, 0); citySizeDescription->height = art_frame_length(frm, 0, 0); art_ptr_unlock(citySizeDescription->handle); citySizeDescription->handle = INVALID_CACHE_ENTRY; citySizeDescription->data = art_ptr_lock_data(fid, 0, 0, &(citySizeDescription->handle)); // FIXME: check is obviously wrong, should be citySizeDescription->data. if (frm == NULL) { return -1; } } fid = art_id(OBJ_TYPE_INTERFACE, 168, 0, 0, 0); frm = art_ptr_lock(fid, &(wmGenData.hotspotNormalFrmHandle)); if (frm == NULL) { return -1; } wmGenData.hotspotFrmWidth = art_frame_width(frm, 0, 0); wmGenData.hotspotFrmHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(wmGenData.hotspotNormalFrmHandle); wmGenData.hotspotNormalFrmHandle = INVALID_CACHE_ENTRY; // hotspot1.frm - town map selector shape #1 fid = art_id(OBJ_TYPE_INTERFACE, 168, 0, 0, 0); wmGenData.hotspotNormalFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.hotspotNormalFrmHandle)); // hotspot2.frm - town map selector shape #2 fid = art_id(OBJ_TYPE_INTERFACE, 223, 0, 0, 0); wmGenData.hotspotPressedFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.hotspotPressedFrmHandle)); if (wmGenData.hotspotPressedFrmData == NULL) { return -1; } // wmaptarg.frm - world map move target maker #1 fid = art_id(OBJ_TYPE_INTERFACE, 139, 0, 0, 0); frm = art_ptr_lock(fid, &(wmGenData.destinationMarkerFrmHandle)); if (frm == NULL) { return -1; } wmGenData.destinationMarkerFrmWidth = art_frame_width(frm, 0, 0); wmGenData.destinationMarkerFrmHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(wmGenData.destinationMarkerFrmHandle); wmGenData.destinationMarkerFrmHandle = INVALID_CACHE_ENTRY; // wmaploc.frm - world map location marker fid = art_id(OBJ_TYPE_INTERFACE, 138, 0, 0, 0); frm = art_ptr_lock(fid, &(wmGenData.locationMarkerFrmHandle)); if (frm == NULL) { return -1; } wmGenData.locationMarkerFrmWidth = art_frame_width(frm, 0, 0); wmGenData.locationMarkerFrmHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(wmGenData.locationMarkerFrmHandle); wmGenData.locationMarkerFrmHandle = INVALID_CACHE_ENTRY; // wmaptarg.frm - world map move target maker #1 fid = art_id(OBJ_TYPE_INTERFACE, 139, 0, 0, 0); wmGenData.destinationMarkerFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.destinationMarkerFrmHandle)); if (wmGenData.destinationMarkerFrmData == NULL) { return -1; } // wmaploc.frm - world map location marker fid = art_id(OBJ_TYPE_INTERFACE, 138, 0, 0, 0); wmGenData.locationMarkerFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.locationMarkerFrmHandle)); if (wmGenData.locationMarkerFrmData == NULL) { return -1; } for (int index = 0; index < WORLD_MAP_ENCOUNTER_FRM_COUNT; index++) { fid = art_id(OBJ_TYPE_INTERFACE, wmRndCursorFids[index], 0, 0, 0); frm = art_ptr_lock(fid, &(wmGenData.encounterCursorFrmHandle[index])); if (frm == NULL) { return -1; } wmGenData.encounterCursorFrmWidths[index] = art_frame_width(frm, 0, 0); wmGenData.encounterCursorFrmHeights[index] = art_frame_length(frm, 0, 0); art_ptr_unlock(wmGenData.encounterCursorFrmHandle[index]); wmGenData.encounterCursorFrmHandle[index] = INVALID_CACHE_ENTRY; wmGenData.encounterCursorFrmData[index] = art_ptr_lock_data(fid, 0, 0, &(wmGenData.encounterCursorFrmHandle[index])); } for (int index = 0; index < wmMaxTileNum; index++) { wmTileInfoList[index].handle = INVALID_CACHE_ENTRY; } // wmtabs.frm - worldmap town tabs underlay fid = art_id(OBJ_TYPE_INTERFACE, 364, 0, 0, 0); frm = art_ptr_lock(fid, &frmHandle); if (frm == NULL) { return -1; } wmGenData.tabsBackgroundFrmWidth = art_frame_width(frm, 0, 0); wmGenData.tabsBackgroundFrmHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(frmHandle); wmGenData.tabsBackgroundFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.tabsBackgroundFrmHandle)) + wmGenData.tabsBackgroundFrmWidth * 27; if (wmGenData.tabsBackgroundFrmData == NULL) { return -1; } // wmtbedge.frm - worldmap town tabs edging overlay fid = art_id(OBJ_TYPE_INTERFACE, 367, 0, 0, 0); wmGenData.tabsBorderFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.tabsBorderFrmHandle)); if (wmGenData.tabsBorderFrmData == NULL) { return -1; } // wmdial.frm - worldmap night/day dial fid = art_id(OBJ_TYPE_INTERFACE, 365, 0, 0, 0); wmGenData.dialFrm = art_ptr_lock(fid, &(wmGenData.dialFrmHandle)); if (wmGenData.dialFrm == NULL) { return -1; } wmGenData.dialFrmWidth = art_frame_width(wmGenData.dialFrm, 0, 0); wmGenData.dialFrmHeight = art_frame_length(wmGenData.dialFrm, 0, 0); // wmscreen - worldmap overlay screen fid = art_id(OBJ_TYPE_INTERFACE, 363, 0, 0, 0); frm = art_ptr_lock(fid, &frmHandle); if (frm == NULL) { return -1; } wmGenData.carImageOverlayFrmWidth = art_frame_width(frm, 0, 0); wmGenData.carImageOverlayFrmHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(frmHandle); wmGenData.carImageOverlayFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.carImageOverlayFrmHandle)); if (wmGenData.carImageOverlayFrmData == NULL) { return -1; } // wmglobe.frm - worldmap globe stamp overlay fid = art_id(OBJ_TYPE_INTERFACE, 366, 0, 0, 0); frm = art_ptr_lock(fid, &frmHandle); if (frm == NULL) { return -1; } wmGenData.globeOverlayFrmWidth = art_frame_width(frm, 0, 0); wmGenData.globeOverlayFrmHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(frmHandle); wmGenData.globeOverlayFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.globeOverlayFrmHandle)); if (wmGenData.globeOverlayFrmData == NULL) { return -1; } // lilredup.frm - little red button up fid = art_id(OBJ_TYPE_INTERFACE, 8, 0, 0, 0); frm = art_ptr_lock(fid, &frmHandle); if (frm == NULL) { return -1; } int littleRedButtonUpWidth = art_frame_width(frm, 0, 0); int littleRedButtonUpHeight = art_frame_length(frm, 0, 0); art_ptr_unlock(frmHandle); wmGenData.littleRedButtonNormalFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.littleRedButtonNormalFrmHandle)); // lilreddn.frm - little red button down fid = art_id(OBJ_TYPE_INTERFACE, 9, 0, 0, 0); wmGenData.littleRedButtonPressedFrmData = art_ptr_lock_data(fid, 0, 0, &(wmGenData.littleRedButtonPressedFrmHandle)); // months.frm - month strings for pip boy fid = art_id(OBJ_TYPE_INTERFACE, 129, 0, 0, 0); wmGenData.monthsFrm = art_ptr_lock(fid, &(wmGenData.monthsFrmHandle)); if (wmGenData.monthsFrm == NULL) { return -1; } // numbers.frm - numbers for the hit points and fatigue counters fid = art_id(OBJ_TYPE_INTERFACE, 82, 0, 0, 0); wmGenData.numbersFrm = art_ptr_lock(fid, &(wmGenData.numbersFrmHandle)); if (wmGenData.numbersFrm == NULL) { return -1; } // create town/world switch button win_register_button(wmBkWin, WM_TOWN_WORLD_SWITCH_X, WM_TOWN_WORLD_SWITCH_Y, littleRedButtonUpWidth, littleRedButtonUpHeight, -1, -1, -1, KEY_UPPERCASE_T, wmGenData.littleRedButtonNormalFrmData, wmGenData.littleRedButtonPressedFrmData, NULL, BUTTON_FLAG_TRANSPARENT); for (int index = 0; index < 7; index++) { wmTownMapSubButtonIds[index] = win_register_button(wmBkWin, 508, 138 + 27 * index, littleRedButtonUpWidth, littleRedButtonUpHeight, -1, -1, -1, KEY_CTRL_F1 + index, wmGenData.littleRedButtonNormalFrmData, wmGenData.littleRedButtonPressedFrmData, NULL, BUTTON_FLAG_TRANSPARENT); } for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { // 200 - uparwon.frm - character editor // 199 - uparwoff.frm - character editor fid = art_id(OBJ_TYPE_INTERFACE, 200 - index, 0, 0, 0); frm = art_ptr_lock(fid, &(wmGenData.scrollUpButtonFrmHandle[index])); if (frm == NULL) { return -1; } wmGenData.scrollUpButtonFrmWidth = art_frame_width(frm, 0, 0); wmGenData.scrollUpButtonFrmHeight = art_frame_length(frm, 0, 0); wmGenData.scrollUpButtonFrmData[index] = art_frame_data(frm, 0, 0); } for (int index = 0; index < WORLDMAP_ARROW_FRM_COUNT; index++) { // 182 - dnarwon.frm - character editor // 181 - dnarwoff.frm - character editor fid = art_id(OBJ_TYPE_INTERFACE, 182 - index, 0, 0, 0); frm = art_ptr_lock(fid, &(wmGenData.scrollDownButtonFrmHandle[index])); if (frm == NULL) { return -1; } wmGenData.scrollDownButtonFrmWidth = art_frame_width(frm, 0, 0); wmGenData.scrollDownButtonFrmHeight = art_frame_length(frm, 0, 0); wmGenData.scrollDownButtonFrmData[index] = art_frame_data(frm, 0, 0); } // Scroll up button. win_register_button(wmBkWin, WM_TOWN_LIST_SCROLL_UP_X, WM_TOWN_LIST_SCROLL_UP_Y, wmGenData.scrollUpButtonFrmWidth, wmGenData.scrollUpButtonFrmHeight, -1, -1, -1, KEY_CTRL_ARROW_UP, wmGenData.scrollUpButtonFrmData[WORLDMAP_ARROW_FRM_NORMAL], wmGenData.scrollUpButtonFrmData[WORLDMAP_ARROW_FRM_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); // Scroll down button. win_register_button(wmBkWin, WM_TOWN_LIST_SCROLL_DOWN_X, WM_TOWN_LIST_SCROLL_DOWN_Y, wmGenData.scrollDownButtonFrmWidth, wmGenData.scrollDownButtonFrmHeight, -1, -1, -1, KEY_CTRL_ARROW_DOWN, wmGenData.scrollDownButtonFrmData[WORLDMAP_ARROW_FRM_NORMAL], wmGenData.scrollDownButtonFrmData[WORLDMAP_ARROW_FRM_PRESSED], NULL, BUTTON_FLAG_TRANSPARENT); if (wmGenData.isInCar) { // wmcarmve.frm - worldmap car movie fid = art_id(OBJ_TYPE_INTERFACE, 433, 0, 0, 0); wmGenData.carImageFrm = art_ptr_lock(fid, &(wmGenData.carImageFrmHandle)); if (wmGenData.carImageFrm == NULL) { return -1; } wmGenData.carImageFrmWidth = art_frame_width(wmGenData.carImageFrm, 0, 0); wmGenData.carImageFrmHeight = art_frame_length(wmGenData.carImageFrm, 0, 0); } add_bk_process(wmMouseBkProc); if (wmMakeTabsLabelList(&wmLabelList, &wmLabelCount) == -1) { return -1; } wmInterfaceWasInitialized = 1; if (wmInterfaceRefresh() == -1) { return -1; } win_draw(wmBkWin); scr_disable(); scr_remove_all(); return 0; } // 0x4C2E44 static int wmInterfaceExit() { int i; TileInfo* tile; remove_bk_process(wmMouseBkProc); if (wmBkArtBuf != NULL) { art_ptr_unlock(wmBkKey); wmBkArtBuf = NULL; } wmBkKey = INVALID_CACHE_ENTRY; if (wmBkWin != -1) { win_delete(wmBkWin); wmBkWin = -1; } if (wmGenData.hotspotNormalFrmHandle != INVALID_CACHE_ENTRY) { art_ptr_unlock(wmGenData.hotspotNormalFrmHandle); } wmGenData.hotspotNormalFrmData = NULL; if (wmGenData.hotspotPressedFrmHandle != INVALID_CACHE_ENTRY) { art_ptr_unlock(wmGenData.hotspotPressedFrmHandle); } wmGenData.hotspotPressedFrmData = NULL; if (wmGenData.destinationMarkerFrmHandle != INVALID_CACHE_ENTRY) { art_ptr_unlock(wmGenData.destinationMarkerFrmHandle); } wmGenData.destinationMarkerFrmData = NULL; if (wmGenData.locationMarkerFrmHandle != INVALID_CACHE_ENTRY) { art_ptr_unlock(wmGenData.locationMarkerFrmHandle); } wmGenData.locationMarkerFrmData = NULL; for (i = 0; i < 4; i++) { if (wmGenData.encounterCursorFrmHandle[i] != INVALID_CACHE_ENTRY) { art_ptr_unlock(wmGenData.encounterCursorFrmHandle[i]); } wmGenData.encounterCursorFrmData[i] = NULL; } for (i = 0; i < CITY_SIZE_COUNT; i++) { CitySizeDescription* citySizeDescription = &(wmSphereData[i]); // FIXME: probably unsafe code, no check for -1 art_ptr_unlock(citySizeDescription->handle); citySizeDescription->handle = INVALID_CACHE_ENTRY; citySizeDescription->data = NULL; } for (i = 0; i < wmMaxTileNum; i++) { tile = &(wmTileInfoList[i]); if (tile->handle != INVALID_CACHE_ENTRY) { art_ptr_unlock(tile->handle); tile->handle = INVALID_CACHE_ENTRY; tile->data = NULL; if (tile->walkMaskData != NULL) { mem_free(tile->walkMaskData); tile->walkMaskData = NULL; } } } if (wmGenData.tabsBackgroundFrmData != NULL) { art_ptr_unlock(wmGenData.tabsBackgroundFrmHandle); wmGenData.tabsBackgroundFrmHandle = INVALID_CACHE_ENTRY; wmGenData.tabsBackgroundFrmData = NULL; } if (wmGenData.tabsBorderFrmData != NULL) { art_ptr_unlock(wmGenData.tabsBorderFrmHandle); wmGenData.tabsBorderFrmHandle = INVALID_CACHE_ENTRY; wmGenData.tabsBorderFrmData = NULL; } if (wmGenData.dialFrm != NULL) { art_ptr_unlock(wmGenData.dialFrmHandle); wmGenData.dialFrmHandle = INVALID_CACHE_ENTRY; wmGenData.dialFrm = NULL; } if (wmGenData.carImageOverlayFrmData != NULL) { art_ptr_unlock(wmGenData.carImageOverlayFrmHandle); wmGenData.carImageOverlayFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageOverlayFrmData = NULL; } if (wmGenData.globeOverlayFrmData != NULL) { art_ptr_unlock(wmGenData.globeOverlayFrmHandle); wmGenData.globeOverlayFrmHandle = INVALID_CACHE_ENTRY; wmGenData.globeOverlayFrmData = NULL; } if (wmGenData.littleRedButtonNormalFrmData != NULL) { art_ptr_unlock(wmGenData.littleRedButtonNormalFrmHandle); wmGenData.littleRedButtonNormalFrmHandle = INVALID_CACHE_ENTRY; wmGenData.littleRedButtonNormalFrmData = NULL; } if (wmGenData.littleRedButtonPressedFrmData != NULL) { art_ptr_unlock(wmGenData.littleRedButtonPressedFrmHandle); wmGenData.littleRedButtonPressedFrmHandle = INVALID_CACHE_ENTRY; wmGenData.littleRedButtonPressedFrmData = NULL; } for (i = 0; i < 2; i++) { art_ptr_unlock(wmGenData.scrollUpButtonFrmHandle[i]); wmGenData.scrollUpButtonFrmHandle[i] = INVALID_CACHE_ENTRY; wmGenData.scrollUpButtonFrmData[i] = NULL; art_ptr_unlock(wmGenData.scrollDownButtonFrmHandle[i]); wmGenData.scrollDownButtonFrmHandle[i] = INVALID_CACHE_ENTRY; wmGenData.scrollDownButtonFrmData[i] = NULL; } wmGenData.scrollUpButtonFrmHeight = 0; wmGenData.scrollDownButtonFrmWidth = 0; wmGenData.scrollDownButtonFrmHeight = 0; wmGenData.scrollUpButtonFrmWidth = 0; if (wmGenData.monthsFrm != NULL) { art_ptr_unlock(wmGenData.monthsFrmHandle); wmGenData.monthsFrmHandle = INVALID_CACHE_ENTRY; wmGenData.monthsFrm = NULL; } if (wmGenData.numbersFrm != NULL) { art_ptr_unlock(wmGenData.numbersFrmHandle); wmGenData.numbersFrmHandle = INVALID_CACHE_ENTRY; wmGenData.numbersFrm = NULL; } if (wmGenData.carImageFrm != NULL) { art_ptr_unlock(wmGenData.carImageFrmHandle); wmGenData.carImageFrmHandle = INVALID_CACHE_ENTRY; wmGenData.carImageFrm = NULL; wmGenData.carImageFrmWidth = 0; wmGenData.carImageFrmHeight = 0; } wmGenData.encounterIconIsVisible = 0; wmGenData.encounterMapId = -1; wmGenData.encounterTableId = -1; wmGenData.encounterEntryId = -1; enable_box_bar_win(); map_enable_bk_processes(); cycle_enable(); text_font(wmGenData.oldFont); // NOTE: Uninline. wmFreeTabsLabelList(&wmLabelList, &wmLabelCount); wmInterfaceWasInitialized = 0; scr_enable(); return 0; } // NOTE: Inlined. // // 0x4C31E8 static int wmInterfaceScroll(int dx, int dy, bool* successPtr) { return wmInterfaceScrollPixel(20, 20, dx, dy, successPtr, 1); } // FIXME: There is small bug in this function. There is [success] flag returned // by reference so that calling code can update scrolling mouse cursor to invalid // range. It works OK on straight directions. But in diagonals when scrolling in // one direction is possible (and in fact occured), it will still be reported as // error. // // 0x4C3200 static int wmInterfaceScrollPixel(int stepX, int stepY, int dx, int dy, bool* success, bool shouldRefresh) { int v6 = wmWorldOffsetY; int v7 = wmWorldOffsetX; if (success != NULL) { *success = true; } if (dy < 0) { if (v6 > 0) { v6 -= stepY; if (v6 < 0) { v6 = 0; } } else { if (success != NULL) { *success = false; } } } else if (dy > 0) { if (v6 < wmGenData.viewportMaxY) { v6 += stepY; if (v6 > wmGenData.viewportMaxY) { v6 = wmGenData.viewportMaxY; } } else { if (success != NULL) { *success = false; } } } if (dx < 0) { if (v7 > 0) { v7 -= stepX; if (v7 < 0) { v7 = 0; } } else { if (success != NULL) { *success = false; } } } else if (dx > 0) { if (v7 < wmGenData.viewportMaxX) { v7 += stepX; if (v7 > wmGenData.viewportMaxX) { v7 = wmGenData.viewportMaxX; } } else { if (success != NULL) { *success = false; } } } wmWorldOffsetY = v6; wmWorldOffsetX = v7; if (shouldRefresh) { if (wmInterfaceRefresh() == -1) { return -1; } } return 0; } // 0x4C32EC static void wmMouseBkProc() { // 0x51DEB0 static unsigned int lastTime = 0; // 0x51DEB4 static bool couldScroll = true; int x; int y; mouse_get_position(&x, &y); int dx = 0; if (x == 639) { dx = 1; } else if (x == 0) { dx = -1; } int dy = 0; if (y == 479) { dy = 1; } else if (y == 0) { dy = -1; } int oldMouseCursor = gmouse_get_cursor(); int newMouseCursor = oldMouseCursor; if (dx != 0 || dy != 0) { if (dx > 0) { if (dy > 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_SE; } else if (dy < 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_NE; } else { newMouseCursor = MOUSE_CURSOR_SCROLL_E; } } else if (dx < 0) { if (dy > 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_SW; } else if (dy < 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_NW; } else { newMouseCursor = MOUSE_CURSOR_SCROLL_W; } } else { if (dy < 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_N; } else if (dy > 0) { newMouseCursor = MOUSE_CURSOR_SCROLL_S; } } unsigned int tick = get_bk_time(); if (elapsed_tocks(tick, lastTime) > 50) { lastTime = get_bk_time(); // NOTE: Uninline. wmInterfaceScroll(dx, dy, &couldScroll); } if (!couldScroll) { newMouseCursor += 8; } } else { if (oldMouseCursor != MOUSE_CURSOR_ARROW) { newMouseCursor = MOUSE_CURSOR_ARROW; } } if (oldMouseCursor != newMouseCursor) { gmouse_set_cursor(newMouseCursor); } } // NOTE: Inlined. // // 0x4C340C static int wmMarkSubTileOffsetVisited(int tile, int subtileX, int subtileY, int offsetX, int offsetY) { return wmMarkSubTileOffsetVisitedFunc(tile, subtileX, subtileY, offsetX, offsetY, SUBTILE_STATE_VISITED); } // NOTE: Inlined. // // 0x4C3420 static int wmMarkSubTileOffsetKnown(int tile, int subtileX, int subtileY, int offsetX, int offsetY) { return wmMarkSubTileOffsetVisitedFunc(tile, subtileX, subtileY, offsetX, offsetY, SUBTILE_STATE_KNOWN); } // 0x4C3434 static int wmMarkSubTileOffsetVisitedFunc(int tile, int subtileX, int subtileY, int offsetX, int offsetY, int subtileState) { int actualTile; int actualSubtileX; int actualSubtileY; TileInfo* tileInfo; SubtileInfo* subtileInfo; actualSubtileX = subtileX + offsetX; actualTile = tile; actualSubtileY = subtileY + offsetY; if (actualSubtileX >= 0) { if (actualSubtileX >= SUBTILE_GRID_WIDTH) { if (tile % wmNumHorizontalTiles == wmNumHorizontalTiles - 1) { return -1; } actualTile = tile + 1; actualSubtileX %= SUBTILE_GRID_WIDTH; } } else { if (!(tile % wmNumHorizontalTiles)) { return -1; } actualSubtileX += SUBTILE_GRID_WIDTH; actualTile = tile - 1; } if (actualSubtileY >= 0) { if (actualSubtileY >= SUBTILE_GRID_HEIGHT) { if (actualTile > wmMaxTileNum - wmNumHorizontalTiles - 1) { return -1; } actualTile += wmNumHorizontalTiles; actualSubtileY %= SUBTILE_GRID_HEIGHT; } } else { if (actualTile < wmNumHorizontalTiles) { return -1; } actualSubtileY += SUBTILE_GRID_HEIGHT; actualTile -= wmNumHorizontalTiles; } tileInfo = &(wmTileInfoList[actualTile]); subtileInfo = &(tileInfo->subtiles[actualSubtileY][actualSubtileX]); if (subtileState != SUBTILE_STATE_KNOWN || subtileInfo->state == SUBTILE_STATE_UNKNOWN) { subtileInfo->state = subtileState; } return 0; } // 0x4C3550 static void wmMarkSubTileRadiusVisited(int x, int y) { int radius = 1; if (perkHasRank(obj_dude, PERK_SCOUT)) { radius = 2; } wmSubTileMarkRadiusVisited(x, y, radius); } // 0x4C35A8 int wmSubTileMarkRadiusVisited(int x, int y, int radius) { int tile; int subtileX; int subtileY; int offsetX; int offsetY; SubtileInfo* subtile; tile = x / WM_TILE_WIDTH % wmNumHorizontalTiles + y / WM_TILE_HEIGHT * wmNumHorizontalTiles; subtileX = x % WM_TILE_WIDTH / WM_SUBTILE_SIZE; subtileY = y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE; for (offsetY = -radius; offsetY <= radius; offsetY++) { for (offsetX = -radius; offsetX <= radius; offsetX++) { // NOTE: Uninline. wmMarkSubTileOffsetKnown(tile, subtileX, subtileY, offsetX, offsetY); } } subtile = &(wmTileInfoList[tile].subtiles[subtileY][subtileX]); subtile->state = SUBTILE_STATE_VISITED; switch (subtile->field_4) { case 2: while (subtileY-- > 0) { // NOTE: Uninline. wmMarkSubTileOffsetVisited(tile, subtileX, subtileY, 0, 0); } break; case 4: while (subtileX-- >= 0) { // NOTE: Uninline. wmMarkSubTileOffsetVisited(tile, subtileX, subtileY, 0, 0); } if (tile % wmNumHorizontalTiles > 0) { for (subtileX = 0; subtileX < SUBTILE_GRID_WIDTH; subtileX++) { // NOTE: Uninline. wmMarkSubTileOffsetVisited(tile - 1, subtileX, subtileY, 0, 0); } } break; } return 0; } // 0x4C3740 int wmSubTileGetVisitedState(int x, int y, int* statePtr) { TileInfo* tile; SubtileInfo* subtile; tile = &(wmTileInfoList[y / WM_TILE_HEIGHT * wmNumHorizontalTiles + x / WM_TILE_WIDTH % wmNumHorizontalTiles]); subtile = &(tile->subtiles[y % WM_TILE_HEIGHT / WM_SUBTILE_SIZE][x % WM_TILE_WIDTH / WM_SUBTILE_SIZE]); *statePtr = subtile->state; return 0; } // Load tile art if needed. // // 0x4C37EC static int wmTileGrabArt(int tileIdx) { TileInfo* tile = &(wmTileInfoList[tileIdx]); if (tile->data != NULL) { return 0; } tile->data = art_ptr_lock_data(tile->fid, 0, 0, &(tile->handle)); if (tile->data != NULL) { return 0; } wmInterfaceExit(); return -1; } // 0x4C3830 static int wmInterfaceRefresh() { if (wmInterfaceWasInitialized != 1) { return 0; } int v17 = wmWorldOffsetX % WM_TILE_WIDTH; int v18 = wmWorldOffsetY % WM_TILE_HEIGHT; int v20 = WM_TILE_HEIGHT - v18; int v21 = WM_TILE_WIDTH * v18; int v19 = WM_TILE_WIDTH - v17; // Render tiles. int y = 0; int x = 0; int v0 = wmWorldOffsetY / WM_TILE_HEIGHT * wmNumHorizontalTiles + wmWorldOffsetX / WM_TILE_WIDTH % wmNumHorizontalTiles; while (y < WM_VIEW_HEIGHT) { x = 0; int v23 = 0; int height; while (x < WM_VIEW_WIDTH) { if (wmTileGrabArt(v0) == -1) { return -1; } int width = WM_TILE_WIDTH; int srcX = 0; if (x == 0) { srcX = v17; width = v19; } if (width + x > WM_VIEW_WIDTH) { width = WM_VIEW_WIDTH - x; } height = WM_TILE_HEIGHT; if (y == 0) { height = v20; srcX += v21; } if (height + y > WM_VIEW_HEIGHT) { height = WM_VIEW_HEIGHT - y; } TileInfo* tileInfo = &(wmTileInfoList[v0]); buf_to_buf(tileInfo->data + srcX, width, height, WM_TILE_WIDTH, wmBkWinBuf + WM_WINDOW_WIDTH * (y + WM_VIEW_Y) + WM_VIEW_X + x, WM_WINDOW_WIDTH); v0++; x += width; v23++; } v0 += wmNumHorizontalTiles - v23; y += height; } // Render cities. for (int index = 0; index < wmMaxAreaNum; index++) { CityInfo* cityInfo = &(wmAreaInfoList[index]); if (cityInfo->state != CITY_STATE_UNKNOWN) { CitySizeDescription* citySizeDescription = &(wmSphereData[cityInfo->size]); int cityX = cityInfo->x - wmWorldOffsetX; int cityY = cityInfo->y - wmWorldOffsetY; if (cityX >= 0 && cityX <= 472 - citySizeDescription->width && cityY >= 0 && cityY <= 465 - citySizeDescription->height) { wmInterfaceDrawCircleOverlay(cityInfo, citySizeDescription, wmBkWinBuf, cityX, cityY); } } } // Hide unknown subtiles, dim unvisited. int v25 = wmWorldOffsetX / WM_TILE_WIDTH % wmNumHorizontalTiles + wmWorldOffsetY / WM_TILE_HEIGHT * wmNumHorizontalTiles; int v30 = 0; while (v30 < WM_VIEW_HEIGHT) { int v24 = 0; int v33 = 0; int v29; while (v33 < WM_VIEW_WIDTH) { int v31 = WM_TILE_WIDTH; if (v33 == 0) { v31 = WM_TILE_WIDTH - v17; } if (v33 + v31 > WM_VIEW_WIDTH) { v31 = WM_VIEW_WIDTH - v33; } v29 = WM_TILE_HEIGHT; if (v30 == 0) { v29 -= v18; } if (v30 + v29 > WM_VIEW_HEIGHT) { v29 = WM_VIEW_HEIGHT - v30; } int v32; if (v30 != 0) { v32 = WM_VIEW_Y; } else { v32 = WM_VIEW_Y - v18; } int v13 = 0; int v34 = v30 + v32; for (int row = 0; row < SUBTILE_GRID_HEIGHT; row++) { int v35; if (v33 != 0) { v35 = WM_VIEW_X; } else { v35 = WM_VIEW_X - v17; } int v15 = v33 + v35; for (int column = 0; column < SUBTILE_GRID_WIDTH; column++) { TileInfo* tileInfo = &(wmTileInfoList[v25]); wmInterfaceDrawSubTileList(tileInfo, column, row, v15, v34, 1); v15 += WM_SUBTILE_SIZE; v35 += WM_SUBTILE_SIZE; } v32 += WM_SUBTILE_SIZE; v34 += WM_SUBTILE_SIZE; } v25++; v24++; v33 += v31; } v25 += wmNumHorizontalTiles - v24; v30 += v29; } wmDrawCursorStopped(); wmRefreshInterfaceOverlay(true); return 0; } // 0x4C3C9C static void wmInterfaceRefreshDate(bool shouldRefreshWindow) { int month; int day; int year; game_time_date(&month, &day, &year); month--; unsigned char* dest = wmBkWinBuf; int numbersFrmWidth = art_frame_width(wmGenData.numbersFrm, 0, 0); int numbersFrmHeight = art_frame_length(wmGenData.numbersFrm, 0, 0); unsigned char* numbersFrmData = art_frame_data(wmGenData.numbersFrm, 0, 0); dest += WM_WINDOW_WIDTH * 12 + 487; buf_to_buf(numbersFrmData + 9 * (day / 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); buf_to_buf(numbersFrmData + 9 * (day % 10), 9, numbersFrmHeight, numbersFrmWidth, dest + 9, WM_WINDOW_WIDTH); int monthsFrmWidth = art_frame_width(wmGenData.monthsFrm, 0, 0); unsigned char* monthsFrmData = art_frame_data(wmGenData.monthsFrm, 0, 0); buf_to_buf(monthsFrmData + monthsFrmWidth * 15 * month, 29, 14, 29, dest + WM_WINDOW_WIDTH + 26, WM_WINDOW_WIDTH); dest += 98; for (int index = 0; index < 4; index++) { dest -= 9; buf_to_buf(numbersFrmData + 9 * (year % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); year /= 10; } int gameTimeHour = game_time_hour(); dest += 72; for (int index = 0; index < 4; index++) { buf_to_buf(numbersFrmData + 9 * (gameTimeHour % 10), 9, numbersFrmHeight, numbersFrmWidth, dest, WM_WINDOW_WIDTH); dest -= 9; gameTimeHour /= 10; } if (shouldRefreshWindow) { Rect rect; rect.ulx = 487; rect.uly = 12; rect.lry = numbersFrmHeight + 12; rect.lrx = 630; win_draw_rect(wmBkWin, &rect); } } // 0x4C3F00 static int wmMatchWorldPosToArea(int x, int y, int* areaIdxPtr) { int v3 = y + WM_VIEW_Y; int v4 = x + WM_VIEW_X; int index; for (index = 0; index < wmMaxAreaNum; index++) { CityInfo* city = &(wmAreaInfoList[index]); if (city->state) { if (v4 >= city->x && v3 >= city->y) { CitySizeDescription* citySizeDescription = &(wmSphereData[city->size]); if (v4 <= city->x + citySizeDescription->width && v3 <= city->y + citySizeDescription->height) { break; } } } } if (index == wmMaxAreaNum) { *areaIdxPtr = -1; } else { *areaIdxPtr = index; } return 0; } // FIXME: This function does not set current font, which is a bit unusual for a // function which draw text. I doubt it was done on purpose, likely simply // forgotten. Because of this, city names are rendered with current font, which // can be any, but in this case it uses default text font, not interface font. // // 0x4C3FA8 static int wmInterfaceDrawCircleOverlay(CityInfo* city, CitySizeDescription* citySizeDescription, unsigned char* dest, int x, int y) { MessageListItem messageListItem; char name[40]; int nameY; int maxY; int width; dark_translucent_trans_buf_to_buf(citySizeDescription->data, citySizeDescription->width, citySizeDescription->height, citySizeDescription->width, dest, x, y, WM_WINDOW_WIDTH, 0x10000, circleBlendTable, commonGrayTable); nameY = y + citySizeDescription->height + 1; maxY = 464 - text_height(); if (nameY < maxY) { if (wmAreaIsKnown(city->areaId)) { // NOTE: Uninline. wmGetAreaName(city, name); } else { strncpy(name, getmsg(&wmMsgFile, &messageListItem, 1004), 40); } width = text_width(name); text_to_buf(dest + WM_WINDOW_WIDTH * nameY + x + citySizeDescription->width / 2 - width / 2, name, width, WM_WINDOW_WIDTH, colorTable[992]); } return 0; } // Helper function that dims specified rectangle in given buffer. It's used to // slightly darken subtile which is known, but not visited. // // 0x4C40A8 static void wmInterfaceDrawSubTileRectFogged(unsigned char* dest, int width, int height, int pitch) { int skipY = pitch - width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned char byte = *dest; *dest++ = intensityColorTable[byte][75]; } dest += skipY; } } // 0x4C40E4 static int wmInterfaceDrawSubTileList(TileInfo* tileInfo, int column, int row, int x, int y, int a6) { SubtileInfo* subtileInfo = &(tileInfo->subtiles[row][column]); int destY = y; int destX = x; int height = WM_SUBTILE_SIZE; if (y < WM_VIEW_Y) { if (y < 0) { height = y + 29; } else { height = WM_SUBTILE_SIZE - (WM_VIEW_Y - y); } destY = WM_VIEW_Y; } if (height + y > 464) { height -= height + y - 464; } int width = WM_SUBTILE_SIZE * a6; if (x < WM_VIEW_X) { destX = WM_VIEW_X; width -= WM_VIEW_X - x; } if (width + x > 472) { width -= width + x - 472; } if (width > 0 && height > 0) { unsigned char* dest = wmBkWinBuf + WM_WINDOW_WIDTH * destY + destX; switch (subtileInfo->state) { case SUBTILE_STATE_UNKNOWN: buf_fill(dest, width, height, WM_WINDOW_WIDTH, colorTable[0]); break; case SUBTILE_STATE_KNOWN: wmInterfaceDrawSubTileRectFogged(dest, width, height, WM_WINDOW_WIDTH); break; } } return 0; } // 0x4C41EC static int wmDrawCursorStopped() { unsigned char* src; int width; int height; if (wmGenData.walkDestinationX >= 1 || wmGenData.walkDestinationY >= 1) { if (wmGenData.encounterIconIsVisible == 1) { src = wmGenData.encounterCursorFrmData[wmGenData.encounterCursorId]; width = wmGenData.encounterCursorFrmWidths[wmGenData.encounterCursorId]; height = wmGenData.encounterCursorFrmHeights[wmGenData.encounterCursorId]; } else { src = wmGenData.locationMarkerFrmData; width = wmGenData.locationMarkerFrmWidth; height = wmGenData.locationMarkerFrmHeight; } if (wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT) { trans_buf_to_buf(src, width, height, width, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.worldPosY - height / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.worldPosX - width / 2, WM_WINDOW_WIDTH); } if (wmGenData.walkDestinationX >= wmWorldOffsetX && wmGenData.walkDestinationX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.walkDestinationY >= wmWorldOffsetY && wmGenData.walkDestinationY < wmWorldOffsetY + WM_VIEW_HEIGHT) { trans_buf_to_buf(wmGenData.destinationMarkerFrmData, wmGenData.destinationMarkerFrmWidth, wmGenData.destinationMarkerFrmHeight, wmGenData.destinationMarkerFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.walkDestinationY - wmGenData.destinationMarkerFrmHeight / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.walkDestinationX - wmGenData.destinationMarkerFrmWidth / 2, WM_WINDOW_WIDTH); } } else { if (wmGenData.encounterIconIsVisible == 1) { src = wmGenData.encounterCursorFrmData[wmGenData.encounterCursorId]; width = wmGenData.encounterCursorFrmWidths[wmGenData.encounterCursorId]; height = wmGenData.encounterCursorFrmHeights[wmGenData.encounterCursorId]; } else { src = wmGenData.mousePressed ? wmGenData.hotspotPressedFrmData : wmGenData.hotspotNormalFrmData; width = wmGenData.hotspotFrmWidth; height = wmGenData.hotspotFrmHeight; } if (wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT) { trans_buf_to_buf(src, width, height, width, wmBkWinBuf + WM_WINDOW_WIDTH * (WM_VIEW_Y - wmWorldOffsetY + wmGenData.worldPosY - height / 2) + WM_VIEW_X - wmWorldOffsetX + wmGenData.worldPosX - width / 2, WM_WINDOW_WIDTH); } } return 0; } // 0x4C4490 static bool wmCursorIsVisible() { return wmGenData.worldPosX >= wmWorldOffsetX && wmGenData.worldPosY >= wmWorldOffsetY && wmGenData.worldPosX < wmWorldOffsetX + WM_VIEW_WIDTH && wmGenData.worldPosY < wmWorldOffsetY + WM_VIEW_HEIGHT; } // NOTE: Inlined. // // 0x4C44D8 static int wmGetAreaName(CityInfo* city, char* name) { MessageListItem messageListItem; getmsg(&map_msg_file, &messageListItem, city->areaId + 1500); strncpy(name, messageListItem.text, 40); return 0; } // Copy city short name. // // 0x4C450C int wmGetAreaIdxName(int areaIdx, char* name) { MessageListItem messageListItem; getmsg(&map_msg_file, &messageListItem, 1500 + areaIdx); strncpy(name, messageListItem.text, 40); return 0; } // Returns true if world area is known. // // 0x4C453C bool wmAreaIsKnown(int cityIdx) { if (!cityIsValid(cityIdx)) { return false; } CityInfo* city = &(wmAreaInfoList[cityIdx]); if (city->visitedState) { if (city->state == CITY_STATE_KNOWN) { return true; } } return false; } // 0x4C457C int wmAreaVisitedState(int areaIdx) { if (!cityIsValid(areaIdx)) { return 0; } CityInfo* city = &(wmAreaInfoList[areaIdx]); if (city->visitedState && city->state == CITY_STATE_KNOWN) { return city->visitedState; } return 0; } // 0x4C45BC bool wmMapIsKnown(int mapIdx) { int cityIdx; if (wmMatchAreaFromMap(mapIdx, &cityIdx) != 0) { return false; } int entranceIdx; if (wmMatchEntranceFromMap(cityIdx, mapIdx, &entranceIdx) != 0) { return false; } CityInfo* city = &(wmAreaInfoList[cityIdx]); EntranceInfo* entrance = &(city->entrances[entranceIdx]); if (entrance->state != 1) { return false; } return true; } // 0x4C4624 int wmAreaMarkVisited(int cityIdx) { return wmAreaMarkVisitedState(cityIdx, CITY_STATE_VISITED); } // 0x4C4634 bool wmAreaMarkVisitedState(int cityIdx, int state) { if (!cityIsValid(cityIdx)) { return false; } CityInfo* city = &(wmAreaInfoList[cityIdx]); int v5 = city->visitedState; if (city->state == CITY_STATE_KNOWN && state != 0) { wmMarkSubTileRadiusVisited(city->x, city->y); } city->visitedState = state; SubtileInfo* subtile; if (wmFindCurSubTileFromPos(city->x, city->y, &subtile) == -1) { return false; } if (state == 1) { subtile->state = SUBTILE_STATE_KNOWN; } else if (state == 2 && v5 == 0) { city->visitedState = 1; } return true; } // 0x4C46CC bool wmAreaSetVisibleState(int cityIdx, int state, bool force) { if (!cityIsValid(cityIdx)) { return false; } CityInfo* city = &(wmAreaInfoList[cityIdx]); if (city->lockState != LOCK_STATE_LOCKED || force) { city->state = state; return true; } return false; } // 0x4C4710 int wmAreaSetWorldPos(int cityIdx, int x, int y) { if (!cityIsValid(cityIdx)) { return -1; } if (x < 0 || x >= WM_TILE_WIDTH * wmNumHorizontalTiles) { return -1; } if (y < 0 || y >= WM_TILE_HEIGHT * (wmMaxTileNum / wmNumHorizontalTiles)) { return -1; } CityInfo* city = &(wmAreaInfoList[cityIdx]); city->x = x; city->y = y; return 0; } // Returns current town x/y. // // 0x4C47A4 int wmGetPartyWorldPos(int* xPtr, int* yPtr) { if (xPtr != NULL) { *xPtr = wmGenData.worldPosX; } if (yPtr != NULL) { *yPtr = wmGenData.worldPosY; } return 0; } // Returns current town. // // 0x4C47C0 int wmGetPartyCurArea(int* areaIdxPtr) { if (areaIdxPtr != NULL) { *areaIdxPtr = wmGenData.currentAreaId; return 0; } return -1; } // 0x4C47D8 static void wmMarkAllSubTiles(int state) { for (int tileIndex = 0; tileIndex < wmMaxTileNum; tileIndex++) { TileInfo* tile = &(wmTileInfoList[tileIndex]); for (int column = 0; column < SUBTILE_GRID_HEIGHT; column++) { for (int row = 0; row < SUBTILE_GRID_WIDTH; row++) { SubtileInfo* subtile = &(tile->subtiles[column][row]); subtile->state = state; } } } } // 0x4C4850 void wmTownMap() { wmWorldMapFunc(1); } // 0x4C485C static int wmTownMapFunc(int* mapIdxPtr) { *mapIdxPtr = -1; if (wmTownMapInit() == -1) { wmTownMapExit(); return -1; } if (wmGenData.currentAreaId == -1) { return -1; } CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); for (;;) { int keyCode = get_input(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit) { break; } if (keyCode != -1) { if (keyCode == KEY_ESCAPE) { break; } if (keyCode >= KEY_1 && keyCode < KEY_1 + city->entrancesLength) { EntranceInfo* entrance = &(city->entrances[keyCode - KEY_1]); *mapIdxPtr = entrance->map; mapSetEntranceInfo(entrance->elevation, entrance->tile, entrance->rotation); break; } if (keyCode >= KEY_CTRL_F1 && keyCode <= KEY_CTRL_F7) { int quickDestinationIndex = wmGenData.tabsOffsetY / 27 + keyCode - KEY_CTRL_F1; if (quickDestinationIndex < wmLabelCount) { int cityIdx = wmLabelList[quickDestinationIndex]; CityInfo* city = &(wmAreaInfoList[cityIdx]); if (!wmAreaIsKnown(city->areaId)) { break; } if (cityIdx != wmGenData.currentAreaId) { wmPartyInitWalking(city->x, city->y); wmGenData.mousePressed = false; break; } } } else { if (keyCode == KEY_CTRL_ARROW_UP) { wmInterfaceScrollTabsStart(-27); } else if (keyCode == KEY_CTRL_ARROW_DOWN) { wmInterfaceScrollTabsStart(27); } else if (keyCode == 2069) { if (wmTownMapRefresh() == -1) { return -1; } } if (keyCode == KEY_UPPERCASE_T || keyCode == KEY_LOWERCASE_T || keyCode == KEY_UPPERCASE_W || keyCode == KEY_LOWERCASE_W) { keyCode = KEY_ESCAPE; } if (keyCode == KEY_ESCAPE) { break; } } } } if (wmTownMapExit() == -1) { return -1; } return 0; } // 0x4C4A6C static int wmTownMapInit() { wmTownMapCurArea = wmGenData.currentAreaId; CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); Art* mapFrm = art_ptr_lock(city->mapFid, &wmTownKey); if (mapFrm == NULL) { return -1; } wmTownWidth = art_frame_width(mapFrm, 0, 0); wmTownHeight = art_frame_length(mapFrm, 0, 0); art_ptr_unlock(wmTownKey); wmTownKey = INVALID_CACHE_ENTRY; wmTownBuffer = art_ptr_lock_data(city->mapFid, 0, 0, &wmTownKey); if (wmTownBuffer == NULL) { return -1; } for (int index = 0; index < city->entrancesLength; index++) { wmTownMapButtonId[index] = -1; } for (int index = 0; index < city->entrancesLength; index++) { EntranceInfo* entrance = &(city->entrances[index]); if (entrance->state == 0) { continue; } if (entrance->x == -1 || entrance->y == -1) { continue; } wmTownMapButtonId[index] = win_register_button(wmBkWin, entrance->x, entrance->y, wmGenData.hotspotFrmWidth, wmGenData.hotspotFrmHeight, -1, 2069, -1, KEY_1 + index, wmGenData.hotspotNormalFrmData, wmGenData.hotspotPressedFrmData, NULL, BUTTON_FLAG_TRANSPARENT); if (wmTownMapButtonId[index] == -1) { return -1; } } remove_bk_process(wmMouseBkProc); if (wmTownMapRefresh() == -1) { return -1; } return 0; } // 0x4C4BD0 static int wmTownMapRefresh() { buf_to_buf(wmTownBuffer, wmTownWidth, wmTownHeight, wmTownWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_VIEW_Y + WM_VIEW_X, WM_WINDOW_WIDTH); wmRefreshInterfaceOverlay(false); CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); for (int index = 0; index < city->entrancesLength; index++) { EntranceInfo* entrance = &(city->entrances[index]); if (entrance->state == 0) { continue; } if (entrance->x == -1 || entrance->y == -1) { continue; } MessageListItem messageListItem; messageListItem.num = 200 + 10 * wmTownMapCurArea + index; if (message_search(&wmMsgFile, &messageListItem)) { if (messageListItem.text != NULL) { int width = text_width(messageListItem.text); win_print(wmBkWin, messageListItem.text, width, wmGenData.hotspotFrmWidth / 2 + entrance->x - width / 2, wmGenData.hotspotFrmHeight + entrance->y + 2, colorTable[992] | 0x2010000); } } } win_draw(wmBkWin); return 0; } // 0x4C4D00 static int wmTownMapExit() { if (wmTownKey != INVALID_CACHE_ENTRY) { art_ptr_unlock(wmTownKey); wmTownKey = INVALID_CACHE_ENTRY; wmTownBuffer = NULL; wmTownWidth = 0; wmTownHeight = 0; } if (wmTownMapCurArea != -1) { CityInfo* city = &(wmAreaInfoList[wmTownMapCurArea]); for (int index = 0; index < city->entrancesLength; index++) { if (wmTownMapButtonId[index] != -1) { win_delete_button(wmTownMapButtonId[index]); wmTownMapButtonId[index] = -1; } } } if (wmInterfaceRefresh() == -1) { return -1; } add_bk_process(wmMouseBkProc); return 0; } // 0x4C4DA4 int wmCarUseGas(int amount) { if (game_get_global_var(GVAR_NEW_RENO_SUPER_CAR) != 0) { amount -= amount * 90 / 100; } if (game_get_global_var(GVAR_NEW_RENO_CAR_UPGRADE) != 0) { amount -= amount * 10 / 100; } if (game_get_global_var(GVAR_CAR_UPGRADE_FUEL_CELL_REGULATOR) != 0) { amount /= 2; } wmGenData.carFuel -= amount; if (wmGenData.carFuel < 0) { wmGenData.carFuel = 0; } return 0; } // Returns amount of fuel that does not fit into tank. // // 0x4C4E34 int wmCarFillGas(int amount) { if ((amount + wmGenData.carFuel) <= CAR_FUEL_MAX) { wmGenData.carFuel += amount; return 0; } int remaining = CAR_FUEL_MAX - wmGenData.carFuel; wmGenData.carFuel = CAR_FUEL_MAX; return remaining; } // 0x4C4E74 int wmCarGasAmount() { return wmGenData.carFuel; } // 0x4C4E7C bool wmCarIsOutOfGas() { return wmGenData.carFuel <= 0; } // 0x4C4E8C int wmCarCurrentArea() { return wmGenData.currentCarAreaId; } // 0x4C4E94 int wmCarGiveToParty() { // 0x4BC880 static MessageListItem messageListItem; if (wmGenData.carFuel <= 0) { // The car is out of power. char* msg = getmsg(&wmMsgFile, &messageListItem, 1502); display_print(msg); return -1; } wmGenData.isInCar = true; MapTransition transition; memset(&transition, 0, sizeof(transition)); transition.map = -2; map_leave_map(&transition); CityInfo* city = &(wmAreaInfoList[CITY_CAR_OUT_OF_GAS]); city->state = CITY_STATE_UNKNOWN; city->visitedState = 0; return 0; } // 0x4C4F28 int wmSfxMaxCount() { int mapIdx = map_get_index_number(); if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); return map->ambientSoundEffectsLength; } // 0x4C4F5C int wmSfxRollNextIdx() { int mapIdx = map_get_index_number(); if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); int totalChances = 0; for (int index = 0; index < map->ambientSoundEffectsLength; index++) { MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]); totalChances += sfx->chance; } int chance = roll_random(0, totalChances); for (int index = 0; index < map->ambientSoundEffectsLength; index++) { MapAmbientSoundEffectInfo* sfx = &(map->ambientSoundEffects[index]); if (chance >= sfx->chance) { chance -= sfx->chance; continue; } return index; } return -1; } // 0x4C5004 int wmSfxIdxName(int sfxIdx, char** namePtr) { if (namePtr == NULL) { return -1; } *namePtr = NULL; int mapIdx = map_get_index_number(); if (mapIdx < 0 || mapIdx >= wmMaxMapNum) { return -1; } MapInfo* map = &(wmMapInfoList[mapIdx]); if (sfxIdx < 0 || sfxIdx >= map->ambientSoundEffectsLength) { return -1; } MapAmbientSoundEffectInfo* ambientSoundEffectInfo = &(map->ambientSoundEffects[sfxIdx]); *namePtr = ambientSoundEffectInfo->name; int v1 = 0; if (strcmp(ambientSoundEffectInfo->name, "brdchir1") == 0) { v1 = 1; } else if (strcmp(ambientSoundEffectInfo->name, "brdchirp") == 0) { v1 = 2; } if (v1 != 0) { int dayPart; int gameTimeHour = game_time_hour(); if (gameTimeHour <= 600 || gameTimeHour >= 1800) { dayPart = DAY_PART_NIGHT; } else if (gameTimeHour >= 1200) { dayPart = DAY_PART_AFTERNOON; } else { dayPart = DAY_PART_MORNING; } if (dayPart == DAY_PART_NIGHT) { *namePtr = wmRemapSfxList[v1 - 1]; } } return 0; } // 0x4C50F4 static int wmRefreshInterfaceOverlay(bool shouldRefreshWindow) { trans_buf_to_buf(wmBkArtBuf, wmBkWidth, wmBkHeight, wmBkWidth, wmBkWinBuf, WM_WINDOW_WIDTH); wmRefreshTabs(); // NOTE: Uninline. wmInterfaceDialSyncTime(false); wmRefreshInterfaceDial(false); if (wmGenData.isInCar) { unsigned char* data = art_frame_data(wmGenData.carImageFrm, wmGenData.carImageCurrentFrameIndex, 0); if (data == NULL) { return -1; } buf_to_buf(data, wmGenData.carImageFrmWidth, wmGenData.carImageFrmHeight, wmGenData.carImageFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_Y + WM_WINDOW_CAR_X, WM_WINDOW_WIDTH); trans_buf_to_buf(wmGenData.carImageOverlayFrmData, wmGenData.carImageOverlayFrmWidth, wmGenData.carImageOverlayFrmHeight, wmGenData.carImageOverlayFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_OVERLAY_Y + WM_WINDOW_CAR_OVERLAY_X, WM_WINDOW_WIDTH); wmInterfaceRefreshCarFuel(); } else { trans_buf_to_buf(wmGenData.globeOverlayFrmData, wmGenData.globeOverlayFrmWidth, wmGenData.globeOverlayFrmHeight, wmGenData.globeOverlayFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_GLOBE_OVERLAY_Y + WM_WINDOW_GLOBE_OVERLAY_X, WM_WINDOW_WIDTH); } wmInterfaceRefreshDate(false); if (shouldRefreshWindow) { win_draw(wmBkWin); } return 0; } // 0x4C5244 static void wmInterfaceRefreshCarFuel() { int ratio = (WM_WINDOW_CAR_FUEL_BAR_HEIGHT * wmGenData.carFuel) / CAR_FUEL_MAX; if ((ratio & 1) != 0) { ratio -= 1; } unsigned char* dest = wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_CAR_FUEL_BAR_Y + WM_WINDOW_CAR_FUEL_BAR_X; for (int index = WM_WINDOW_CAR_FUEL_BAR_HEIGHT; index > ratio; index--) { *dest = 14; dest += 640; } while (ratio > 0) { *dest = 196; dest += WM_WINDOW_WIDTH; *dest = 14; dest += WM_WINDOW_WIDTH; ratio -= 2; } } // 0x4C52B0 static int wmRefreshTabs() { unsigned char* v30; unsigned char* v0; int v31; CityInfo* city; Art* art; CacheEntry* cache_entry; int width; int height; unsigned char* buf; int v10; unsigned char* v11; unsigned char* v12; int v32; unsigned char* v13; trans_buf_to_buf(wmGenData.tabsBackgroundFrmData + wmGenData.tabsBackgroundFrmWidth * wmGenData.tabsOffsetY + 9, 119, 178, wmGenData.tabsBackgroundFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH); v30 = wmBkWinBuf + WM_WINDOW_WIDTH * 138 + 530; v0 = wmBkWinBuf + WM_WINDOW_WIDTH * 138 + 530 - WM_WINDOW_WIDTH * (wmGenData.tabsOffsetY % 27); v31 = wmGenData.tabsOffsetY / 27; if (v31 < wmLabelCount) { city = &(wmAreaInfoList[wmLabelList[v31]]); if (city->labelFid != -1) { art = art_ptr_lock(city->labelFid, &cache_entry); if (art == NULL) { return -1; } width = art_frame_width(art, 0, 0); height = art_frame_length(art, 0, 0); buf = art_frame_data(art, 0, 0); if (buf == NULL) { return -1; } v10 = height - wmGenData.tabsOffsetY % 27; v11 = buf + width * (wmGenData.tabsOffsetY % 27); v12 = v0; if (v0 < v30 - WM_WINDOW_WIDTH) { v12 = v30 - WM_WINDOW_WIDTH; } buf_to_buf(v11, width, v10, width, v12, WM_WINDOW_WIDTH); art_ptr_unlock(cache_entry); cache_entry = INVALID_CACHE_ENTRY; } } v13 = v0 + WM_WINDOW_WIDTH * 27; v32 = v31 + 6; for (int v14 = v31 + 1; v14 < v32; v14++) { if (v14 < wmLabelCount) { city = &(wmAreaInfoList[wmLabelList[v14]]); if (city->labelFid != -1) { art = art_ptr_lock(city->labelFid, &cache_entry); if (art == NULL) { return -1; } width = art_frame_width(art, 0, 0); height = art_frame_length(art, 0, 0); buf = art_frame_data(art, 0, 0); if (buf == NULL) { return -1; } buf_to_buf(buf, width, height, width, v13, WM_WINDOW_WIDTH); art_ptr_unlock(cache_entry); cache_entry = INVALID_CACHE_ENTRY; } } v13 += WM_WINDOW_WIDTH * 27; } if (v31 + 6 < wmLabelCount) { city = &(wmAreaInfoList[wmLabelList[v31 + 6]]); if (city->labelFid != -1) { art = art_ptr_lock(city->labelFid, &cache_entry); if (art == NULL) { return -1; } width = art_frame_width(art, 0, 0); height = art_frame_length(art, 0, 0); buf = art_frame_data(art, 0, 0); if (buf == NULL) { return -1; } buf_to_buf(buf, width, height, width, v13, WM_WINDOW_WIDTH); art_ptr_unlock(cache_entry); cache_entry = INVALID_CACHE_ENTRY; } } trans_buf_to_buf(wmGenData.tabsBorderFrmData, 119, 178, 119, wmBkWinBuf + WM_WINDOW_WIDTH * 135 + 501, WM_WINDOW_WIDTH); return 0; } // Creates array of cities available as quick destinations. // // 0x4C55D4 static int wmMakeTabsLabelList(int** quickDestinationsPtr, int* quickDestinationsLengthPtr) { int* quickDestinations = *quickDestinationsPtr; // NOTE: Uninline. wmFreeTabsLabelList(quickDestinationsPtr, quickDestinationsLengthPtr); int capacity = 10; quickDestinations = (int*)mem_malloc(sizeof(*quickDestinations) * capacity); *quickDestinationsPtr = quickDestinations; if (quickDestinations == NULL) { return -1; } int quickDestinationsLength = *quickDestinationsLengthPtr; for (int index = 0; index < wmMaxAreaNum; index++) { if (wmAreaIsKnown(index) && wmAreaInfoList[index].labelFid != -1) { quickDestinationsLength++; *quickDestinationsLengthPtr = quickDestinationsLength; if (capacity <= quickDestinationsLength) { capacity += 10; quickDestinations = (int*)mem_realloc(quickDestinations, sizeof(*quickDestinations) * capacity); if (quickDestinations == NULL) { return -1; } *quickDestinationsPtr = quickDestinations; } quickDestinations[quickDestinationsLength - 1] = index; } } qsort(quickDestinations, quickDestinationsLength, sizeof(*quickDestinations), wmTabsCompareNames); return 0; } // 0x4C56C8 static int wmTabsCompareNames(const void* a1, const void* a2) { int v1 = *(int*)a1; int v2 = *(int*)a2; CityInfo* city1 = &(wmAreaInfoList[v1]); CityInfo* city2 = &(wmAreaInfoList[v2]); return stricmp(city1->name, city2->name); } // NOTE: Inlined. // // 0x4C5710 static int wmFreeTabsLabelList(int** quickDestinationsListPtr, int* quickDestinationsLengthPtr) { if (*quickDestinationsListPtr != NULL) { mem_free(*quickDestinationsListPtr); *quickDestinationsListPtr = NULL; } *quickDestinationsLengthPtr = 0; return 0; } // 0x4C5734 static void wmRefreshInterfaceDial(bool shouldRefreshWindow) { unsigned char* data = art_frame_data(wmGenData.dialFrm, wmGenData.dialFrmCurrentFrameIndex, 0); trans_buf_to_buf(data, wmGenData.dialFrmWidth, wmGenData.dialFrmHeight, wmGenData.dialFrmWidth, wmBkWinBuf + WM_WINDOW_WIDTH * WM_WINDOW_DIAL_Y + WM_WINDOW_DIAL_X, WM_WINDOW_WIDTH); if (shouldRefreshWindow) { Rect rect; rect.ulx = WM_WINDOW_DIAL_X; rect.uly = WM_WINDOW_DIAL_Y - 1; rect.lrx = rect.ulx + wmGenData.dialFrmWidth; rect.lry = rect.uly + wmGenData.dialFrmHeight; win_draw_rect(wmBkWin, &rect); } } // NOTE: Inlined. // // 0x4C57BC static void wmInterfaceDialSyncTime(bool shouldRefreshWindow) { int gameHour; int frame; gameHour = game_time_hour(); frame = (gameHour / 100 + 12) % art_frame_max_frame(wmGenData.dialFrm); if (frame != wmGenData.dialFrmCurrentFrameIndex) { wmGenData.dialFrmCurrentFrameIndex = frame; wmRefreshInterfaceDial(shouldRefreshWindow); } } // 0x4C5804 static int wmAreaFindFirstValidMap(int* mapIdxPtr) { *mapIdxPtr = -1; if (wmGenData.currentAreaId == -1) { return -1; } CityInfo* city = &(wmAreaInfoList[wmGenData.currentAreaId]); if (city->entrancesLength == 0) { return -1; } for (int index = 0; index < city->entrancesLength; index++) { EntranceInfo* entrance = &(city->entrances[index]); if (entrance->state != 0) { *mapIdxPtr = entrance->map; return 0; } } EntranceInfo* entrance = &(city->entrances[0]); entrance->state = 1; *mapIdxPtr = entrance->map; return 0; } // 0x4C58C0 int wmMapMusicStart() { do { int mapIdx = map_get_index_number(); if (mapIdx == -1 || mapIdx >= wmMaxMapNum) { break; } MapInfo* map = &(wmMapInfoList[mapIdx]); if (strlen(map->music) == 0) { break; } if (gsound_background_play_level_music(map->music, 12) == -1) { break; } return 0; } while (0); debug_printf("\nWorldMap Error: Couldn't start map Music!"); return -1; } // 0x4C5928 int wmSetMapMusic(int mapIdx, const char* name) { if (mapIdx == -1 || mapIdx >= wmMaxMapNum) { return -1; } if (name == NULL) { return -1; } debug_printf("\nwmSetMapMusic: %d, %s", mapIdx, name); MapInfo* map = &(wmMapInfoList[mapIdx]); strncpy(map->music, name, 40); map->music[39] = '\0'; if (map_get_index_number() == mapIdx) { gsound_background_stop(); wmMapMusicStart(); } return 0; } // 0x4C59A4 int wmMatchAreaContainingMapIdx(int mapIdx, int* cityIdxPtr) { *cityIdxPtr = 0; for (int cityIdx = 0; cityIdx < wmMaxAreaNum; cityIdx++) { CityInfo* cityInfo = &(wmAreaInfoList[cityIdx]); for (int entranceIdx = 0; entranceIdx < cityInfo->entrancesLength; entranceIdx++) { EntranceInfo* entranceInfo = &(cityInfo->entrances[entranceIdx]); if (entranceInfo->map == mapIdx) { *cityIdxPtr = cityIdx; return 0; } } } return -1; } // 0x4C5A1C int wmTeleportToArea(int cityIdx) { if (!cityIsValid(cityIdx)) { return -1; } wmGenData.currentAreaId = cityIdx; wmGenData.walkDestinationX = 0; wmGenData.walkDestinationY = 0; wmGenData.isWalking = false; CityInfo* city = &(wmAreaInfoList[cityIdx]); wmGenData.worldPosX = city->x; wmGenData.worldPosY = city->y; return 0; } // TODO: Remove. static bool cityIsValid(int areaIdx) { return areaIdx >= 0 && areaIdx < wmMaxAreaNum; } ================================================ FILE: src/game/worldmap.h ================================================ #ifndef FALLOUT_GAME_WORLDMAP_H_ #define FALLOUT_GAME_WORLDMAP_H_ #include #include "plib/db/db.h" #define CAR_FUEL_MAX (80000) typedef enum MapFlags { MAP_SAVED = 0x01, MAP_DEAD_BODIES_AGE = 0x02, MAP_PIPBOY_ACTIVE = 0x04, MAP_CAN_REST_ELEVATION_0 = 0x08, MAP_CAN_REST_ELEVATION_1 = 0x10, MAP_CAN_REST_ELEVATION_2 = 0x20, } MapFlags; typedef enum CityState { CITY_STATE_UNKNOWN, CITY_STATE_KNOWN, CITY_STATE_VISITED, CITY_STATE_INVISIBLE = -66, } CityState; typedef enum City { CITY_ARROYO, CITY_DEN, CITY_KLAMATH, CITY_MODOC, CITY_VAULT_CITY, CITY_GECKO, CITY_BROKEN_HILLS, CITY_NEW_RENO, CITY_SIERRA_ARMY_BASE, CITY_VAULT_15, CITY_NEW_CALIFORNIA_REPUBLIC, CITY_VAULT_13, CITY_MILITARY_BASE, CITY_REDDING, CITY_SAN_FRANCISCO, CITY_NAVARRO, CITY_ENCLAVE, CITY_ABBEY, CITY_PRIMITIVE_TRIBE, CITY_ENV_PROTECTION_AGENCY, CITY_MODOC_GHOST_TOWN, CITY_CAR_OUT_OF_GAS, CITY_DESTROYED_ARROYO, CITY_KLAMATH_TOXIC_CAVES, CITY_DEN_SLAVE_RUN, CITY_RAIDERS, CITY_RANDOM_ENCOUNTER_DESERT, CITY_RANDOM_ENCOUNTER_MOUNTAIN, CITY_RANDOM_ENCOUNTER_CITY, CITY_RANDOM_ENCOUNTER_COAST, CITY_GOLGOTHA, CITY_SPECIAL_ENCOUNTER_WHALE, CITY_SPECIAL_ENCOUNTER_TIN_WOODSMAN, CITY_SPECIAL_ENCOUNTER_BIG_HEAD, CITY_SPECIAL_ENCOUNTER_FEDERATION_SHUTTLE, CITY_SPECIAL_ENCOUNTER_UNWASHED_VILLAGERS, CITY_SPECIAL_ENCOUNTER_MONTY_PYTHON_BRIDGE, CITY_SPECIAL_ENCOUNTER_CAFE_OF_BROKEN_DREAMS, CITY_SPECIAL_ENCOUNTER_HOLY_HAND_GRANADE_I, CITY_SPECIAL_ENCOUNTER_HOLY_HAND_GRANADE_II, CITY_SPECIAL_ENCOUNTER_GUARDIAN_OF_FOREVER, CITY_SPECIAL_ENCOUNTER_TOXIC_WASTE_DUMP, CITY_SPECIAL_ENCOUNTER_PARIAHS, CITY_SPECIAL_ENCOUNTER_MAD_COWS, CITY_CARAVAN_ENCOUNTERS, CITY_FAKE_VAULT_13_A, CITY_FAKE_VAULT_13_B, CITY_SHADOW_WORLDS, CITY_RENO_STABLES, CITY_COUNT, } City; typedef enum Map { MAP_RND_DESERT_1 = 0, MAP_RND_DESERT_2 = 1, MAP_RND_DESERT_3 = 2, MAP_ARROYO_CAVES = 3, MAP_ARROYO_VILLAGE = 4, MAP_ARROYO_BRIDGE = 5, MAP_DEN_ENTRANCE = 6, MAP_DEN_BUSINESS = 7, MAP_DEN_RESIDENTIAL = 8, MAP_KLAMATH_1 = 9, MAP_KLAMATH_MALL = 10, MAP_KLAMATH_RATCAVES = 11, MAP_KLAMATH_TOXICCAVES = 12, MAP_KLAMATH_TRAPCAVES = 13, MAP_KLAMATH_GRAZE = 14, MAP_VAULTCITY_COURTYARD = 15, MAP_VAULTCITY_DOWNTOWN = 16, MAP_VAULTCITY_COUNCIL = 17, MAP_MODOC_MAINSTREET = 18, MAP_MODOC_BEDNBREAKFAST = 19, MAP_MODOC_BRAHMINPASTURES = 20, MAP_MODOC_GARDEN = 21, MAP_MODOC_DOWNTHESHITTER = 22, MAP_MODOC_WELL = 23, MAP_GHOST_FARM = 24, MAP_GHOST_CAVERN = 25, MAP_GHOST_LAKE = 26, MAP_SIERRA_BATTLE = 27, MAP_SIERRA_123 = 28, MAP_SIERRA_4 = 29, MAP_VAULT_CITY_VAULT = 30, MAP_GECKO_SETTLEMENT = 31, MAP_GECKO_POWER_PLANT = 32, MAP_GECKO_JUNKYARD = 33, MAP_GECKO_ACCESS_TUNNELS = 34, MAP_ARROYO_WILDERNESS = 35, MAP_VAULT_15 = 36, MAP_THE_SQUAT_A = 37, MAP_THE_SQUAT_B = 38, MAP_VAULT_15_EAST_ENTRANCE = 39, MAP_VAULT_13 = 40, MAP_VAULT_13_ENTRANCE = 41, MAP_NCR_DOWNTOWN = 42, MAP_NCR_COUNCIL_1 = 43, MAP_NCR_WESTIN_RANCH = 44, MAP_NCR_GRAZING_LANDS = 45, MAP_NCR_BAZAAR = 46, MAP_NCR_COUNCIL_2 = 47, MAP_KLAMATH_CANYON = 48, MAP_MILITARY_BASE_12 = 49, MAP_MILITARY_BASE_34 = 50, MAP_MILITARY_BASE_ENTRANCE = 51, MAP_DEN_SLAVE_RUN = 52, MAP_CAR_DESERT = 53, MAP_NEW_RENO_1 = 54, MAP_NEW_RENO_2 = 55, MAP_NEW_RENO_3 = 56, MAP_NEW_RENO_4 = 57, MAP_NEW_RENO_CHOP_SHOP = 58, MAP_NEW_RENO_GOLGATHA = 59, MAP_NEW_RENO_STABLES = 60, MAP_NEW_RENO_BOXING = 61, MAP_REDDING_WANAMINGO_ENT = 62, MAP_REDDING_WANAMINGO_12 = 63, MAP_REDDING_DOWNTOWN = 64, MAP_REDDING_MINE_ENT = 65, MAP_REDDING_DTOWN_TUNNEL = 66, MAP_REDDING_MINE_TUNNEL = 67, MAP_RND_CITY1 = 68, MAP_RND_CAVERN0 = 69, MAP_RND_CAVERN1 = 70, MAP_RND_CAVERN2 = 71, MAP_RND_CAVERN3 = 72, MAP_RND_CAVERN4 = 73, MAP_RND_MOUNTAIN1 = 74, MAP_RND_MOUNTAIN2 = 75, MAP_RND_COAST1 = 76, MAP_RND_COAST2 = 77, MAP_BROKEN_HILLS1 = 78, MAP_BROKEN_HILLS2 = 79, MAP_RND_CAVERN5 = 80, MAP_RND_DESERT4 = 81, MAP_RND_DESERT5 = 82, MAP_RND_DESERT6 = 83, MAP_RND_DESERT7 = 84, MAP_RND_COAST3 = 85, MAP_RND_COAST4 = 86, MAP_RND_COAST5 = 87, MAP_RND_COAST6 = 88, MAP_RND_COAST7 = 89, MAP_RND_COAST8 = 90, MAP_RND_COAST9 = 91, MAP_RAIDERS_CAMP1 = 92, MAP_RAIDERS_CAMP2 = 93, MAP_BH_RND_DESERT = 94, MAP_BH_RND_MOUNTAIN = 95, MAP_SPECIAL_RND_WHALE = 96, MAP_SPECIAL_RND_WOODSMAN = 97, MAP_SPECIAL_RND_HEAD = 98, MAP_SPECIAL_RND_SHUTTLE = 99, MAP_SPECIAL_RND_UNWASHED = 100, MAP_SPECIAL_RND_BRIDGE = 101, MAP_SPECIAL_RND_CAFE = 102, MAP_SPECIAL_RND_HOLY1 = 103, MAP_SPECIAL_RND_HOLY2 = 104, MAP_SPECIAL_RND_GUARDIAN = 105, MAP_SPECIAL_RND_TOXIC = 106, MAP_SPECIAL_RND_PARIAH = 107, MAP_SPECIAL_RND_MAD_COW = 108, MAP_NAVARRO_ENTRANCE = 109, MAP_RND_COAST_10 = 110, MAP_RND_COAST_11 = 111, MAP_RND_COAST_12 = 112, MAP_RND_DESERT_8 = 113, MAP_RND_DESERT_9 = 114, MAP_RND_DESERT_10 = 115, MAP_RND_DESERT_11 = 116, MAP_RND_DESERT_12 = 117, MAP_RND_CAVERN_5 = 118, MAP_RND_CAVERN_6 = 119, MAP_RND_CAVERN_7 = 120, MAP_RND_MOUNTAIN_3 = 121, MAP_RND_MOUNTAIN_4 = 122, MAP_RND_MOUNTAIN_5 = 123, MAP_RND_MOUNTAIN_6 = 124, MAP_RND_CITY_2 = 125, MAP_ARROYO_TEMPLE = 126, MAP_DESTROYED_ARROYO_BRIDGE = 127, MAP_ENCLAVE_DETENTION = 128, MAP_ENCLAVE_DOCK = 129, MAP_ENCLAVE_END_FIGHT = 130, MAP_ENCLAVE_BARRACKS = 131, MAP_ENCLAVE_PRESIDENT = 132, MAP_ENCLAVE_REACTOR = 133, MAP_ENCLAVE_TRAP_ROOM = 134, MAP_SAN_FRAN_TANKER = 135, MAP_SAN_FRAN_DOCK = 136, MAP_SAN_FRAN_CHINATOWN = 137, MAP_SHUTTLE_EXTERIOR = 138, MAP_SHUTTLE_INTERIOR = 139, MAP_ELRONOLOGIST_BASE = 140, MAP_RND_CITY_3 = 141, MAP_RND_CITY_4 = 142, MAP_RND_CITY_5 = 143, MAP_RND_CITY_6 = 144, MAP_RND_CITY_7 = 145, MAP_RND_CITY_8 = 146, MAP_NEW_RENO_VB = 147, MAP_SHI_TEMPLE = 148, MAP_IN_GAME_MOVIE1 = 149, } Map; extern unsigned char* circleBlendTable; int wmWorldMap_init(); void wmWorldMap_exit(); int wmWorldMap_reset(); int wmWorldMap_save(File* stream); int wmWorldMap_load(File* stream); int wmMapMaxCount(); int wmMapIdxToName(int mapIdx, char* dest); int wmMapMatchNameToIdx(char* name); bool wmMapIdxIsSaveable(int mapIdx); bool wmMapIsSaveable(); bool wmMapDeadBodiesAge(); bool wmMapCanRestHere(int elevation); bool wmMapPipboyActive(); int wmMapMarkVisited(int mapIdx); int wmMapMarkMapEntranceState(int mapIdx, int elevation, int state); void wmWorldMap(); int wmCheckGameAreaEvents(); int wmSetupRandomEncounter(); bool wmEvalTileNumForPlacement(int tile); int wmSubTileMarkRadiusVisited(int x, int y, int radius); int wmSubTileGetVisitedState(int x, int y, int* statePtr); int wmGetAreaIdxName(int areaIdx, char* name); bool wmAreaIsKnown(int areaIdx); int wmAreaVisitedState(int areaIdx); bool wmMapIsKnown(int mapIdx); int wmAreaMarkVisited(int areaIdx); bool wmAreaMarkVisitedState(int areaIdx, int state); bool wmAreaSetVisibleState(int areaIdx, int state, bool force); int wmAreaSetWorldPos(int areaIdx, int x, int y); int wmGetPartyWorldPos(int* xPtr, int* yPtr); int wmGetPartyCurArea(int* areaIdxPtr); void wmTownMap(); int wmCarUseGas(int amount); int wmCarFillGas(int amout); int wmCarGasAmount(); bool wmCarIsOutOfGas(); int wmCarCurrentArea(); int wmCarGiveToParty(); int wmSfxMaxCount(); int wmSfxRollNextIdx(); int wmSfxIdxName(int sfxIdx, char** namePtr); int wmMapMusicStart(); int wmSetMapMusic(int mapIdx, const char* name); int wmMatchAreaContainingMapIdx(int mapIdx, int* areaIdxPtr); int wmTeleportToArea(int areaIdx); #endif /* FALLOUT_GAME_WORLDMAP_H_ */ ================================================ FILE: src/int/audio.c ================================================ #include "int/audio.h" #include #include #include #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "int/memdbg.h" #include "int/sound.h" static bool defaultCompressionFunc(char* filePath); static int decodeRead(int fileHandle, void* buf, unsigned int size); // 0x5108BC static AudioFileIsCompressedProc* queryCompressedFunc = defaultCompressionFunc; // 0x56CB00 static int numAudio; // 0x56CB04 static AudioFile* audio; // 0x41A2B0 bool defaultCompressionFunc(char* filePath) { char* pch = strrchr(filePath, '.'); if (pch != NULL) { strcpy(pch + 1, "war"); } return false; } // 0x41A2D0 int decodeRead(int fileHandle, void* buffer, unsigned int size) { return db_fread(buffer, 1, size, (File*)fileHandle); } // 0x41A2EC int audioOpen(const char* fname, int flags, ...) { char path[80]; sprintf(path, fname); int compression; if (queryCompressedFunc(path)) { compression = 2; } else { compression = 0; } char mode[4]; memset(mode, 0, 4); // NOTE: Original implementation is slightly different, it uses separate // variable to track index where to set 't' and 'b'. char* pm = mode; if (flags & 1) { *pm++ = 'w'; } else if (flags & 2) { *pm++ = 'w'; *pm++ = '+'; } else { *pm++ = 'r'; } if (flags & 0x100) { *pm++ = 't'; } else if (flags & 0x200) { *pm++ = 'b'; } File* stream = db_fopen(path, mode); if (stream == NULL) { debug_printf("AudioOpen: Couldn't open %s for read\n", path); return -1; } int index; for (index = 0; index < numAudio; index++) { if ((audio[index].flags & AUDIO_FILE_IN_USE) == 0) { break; } } if (index == numAudio) { if (audio != NULL) { audio = (AudioFile*)myrealloc(audio, sizeof(*audio) * (numAudio + 1), __FILE__, __LINE__); // "..\int\audio.c", 216 } else { audio = (AudioFile*)mymalloc(sizeof(*audio), __FILE__, __LINE__); // "..\int\audio.c", 218 } numAudio++; } AudioFile* audioFile = &(audio[index]); audioFile->flags = AUDIO_FILE_IN_USE; audioFile->fileHandle = (int)stream; if (compression == 2) { audioFile->flags |= AUDIO_FILE_COMPRESSED; audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); audioFile->fileSize *= 2; } else { audioFile->fileSize = db_filelength(stream); } audioFile->position = 0; return index + 1; } // 0x41A50C int audioCloseFile(int fileHandle) { AudioFile* audioFile = &(audio[fileHandle - 1]); db_fclose((File*)audioFile->fileHandle); if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) { soundDecoderFree(audioFile->soundDecoder); } memset(audioFile, 0, sizeof(AudioFile)); return 0; } // 0x41A574 int audioRead(int fileHandle, void* buffer, unsigned int size) { AudioFile* audioFile = &(audio[fileHandle - 1]); int bytesRead; if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) { bytesRead = soundDecoderDecode(audioFile->soundDecoder, buffer, size); } else { bytesRead = db_fread(buffer, 1, size, (File*)audioFile->fileHandle); } audioFile->position += bytesRead; return bytesRead; } // 0x41A5E0 long audioSeek(int fileHandle, long offset, int origin) { int pos; unsigned char* buf; int v10; AudioFile* audioFile = &(audio[fileHandle - 1]); switch (origin) { case SEEK_SET: pos = offset; break; case SEEK_CUR: pos = offset + audioFile->position; break; case SEEK_END: pos = offset + audioFile->fileSize; break; default: assert(false && "Should be unreachable"); } if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) { if (pos < audioFile->position) { soundDecoderFree(audioFile->soundDecoder); db_fseek((File*)audioFile->fileHandle, 0, SEEK_SET); audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); audioFile->position = 0; audioFile->fileSize *= 2; if (pos != 0) { buf = (unsigned char*)mymalloc(4096, __FILE__, __LINE__); // "..\int\audio.c", 361 while (pos > 4096) { pos -= 4096; audioRead(fileHandle, buf, 4096); } if (pos != 0) { audioRead(fileHandle, buf, pos); } myfree(buf, __FILE__, __LINE__); // // "..\int\audio.c", 367 } } else { buf = (unsigned char*)mymalloc(1024, __FILE__, __LINE__); // "..\int\audio.c", 321 v10 = audioFile->position - pos; while (v10 > 1024) { v10 -= 1024; audioRead(fileHandle, buf, 1024); } if (v10 != 0) { audioRead(fileHandle, buf, v10); } // TODO: Probably leaks memory. } return audioFile->position; } else { return db_fseek((File*)audioFile->fileHandle, offset, origin); } } // 0x41A78C long audioFileSize(int fileHandle) { AudioFile* audioFile = &(audio[fileHandle - 1]); return audioFile->fileSize; } // 0x41A7A8 long audioTell(int fileHandle) { AudioFile* audioFile = &(audio[fileHandle - 1]); return audioFile->position; } // 0x41A7C4 int audioWrite(int handle, const void* buf, unsigned int size) { debug_printf("AudioWrite shouldn't be ever called\n"); return 0; } // 0x41A7D4 int initAudio(AudioFileIsCompressedProc* isCompressedProc) { queryCompressedFunc = isCompressedProc; audio = NULL; numAudio = 0; return soundSetDefaultFileIO(audioOpen, audioCloseFile, audioRead, audioWrite, audioSeek, audioTell, audioFileSize); } // 0x41A818 void audioClose() { if (audio != NULL) { myfree(audio, __FILE__, __LINE__); // "..\int\audio.c", 406 } numAudio = 0; audio = NULL; } ================================================ FILE: src/int/audio.h ================================================ #ifndef FALLOUT_INT_AUDIO_H_ #define FALLOUT_INT_AUDIO_H_ #include "int/audiof.h" int audioOpen(const char* fname, int mode, ...); int audioCloseFile(int fileHandle); int audioRead(int fileHandle, void* buffer, unsigned int size); long audioSeek(int fileHandle, long offset, int origin); long audioFileSize(int fileHandle); long audioTell(int fileHandle); int audioWrite(int handle, const void* buf, unsigned int size); int initAudio(AudioFileIsCompressedProc* isCompressedProc); void audioClose(); #endif /* FALLOUT_INT_AUDIO_H_ */ ================================================ FILE: src/int/audiof.c ================================================ #include "int/audiof.h" #include #include #include #include #define WIN32_LEAN_AND_MEAN #include #include "plib/gnw/debug.h" #include "int/memdbg.h" #include "int/sound.h" static_assert(sizeof(AudioFile) == 28, "wrong size"); static bool defaultCompressionFunc(char* filePath); static int decodeRead(int fileHandle, void* buffer, unsigned int size); // 0x5108C0 static AudioFileIsCompressedProc* queryCompressedFunc = defaultCompressionFunc; // 0x56CB10 static AudioFile* audiof; // 0x56CB14 static int numAudiof; // 0x41A850 static bool defaultCompressionFunc(char* filePath) { char* pch = strrchr(filePath, '.'); if (pch != NULL) { strcpy(pch + 1, "war"); } return false; } // 0x41A870 static int decodeRead(int fileHandle, void* buffer, unsigned int size) { return fread(buffer, 1, size, (FILE*)fileHandle); } // 0x41A88C int audiofOpen(const char* fname, int flags, ...) { char path[MAX_PATH]; strcpy(path, fname); int compression; if (queryCompressedFunc(path)) { compression = 2; } else { compression = 0; } char mode[4]; memset(mode, '\0', 4); // NOTE: Original implementation is slightly different, it uses separate // variable to track index where to set 't' and 'b'. char* pm = mode; if (flags & 0x01) { *pm++ = 'w'; } else if (flags & 0x02) { *pm++ = 'w'; *pm++ = '+'; } else { *pm++ = 'r'; } if (flags & 0x0100) { *pm++ = 't'; } else if (flags & 0x0200) { *pm++ = 'b'; } FILE* stream = fopen(path, mode); if (stream == NULL) { return -1; } int index; for (index = 0; index < numAudiof; index++) { if ((audiof[index].flags & AUDIO_FILE_IN_USE) == 0) { break; } } if (index == numAudiof) { if (audiof != NULL) { audiof = (AudioFile*)myrealloc(audiof, sizeof(*audiof) * (numAudiof + 1), __FILE__, __LINE__); // "..\int\audiof.c", 207 } else { audiof = (AudioFile*)mymalloc(sizeof(*audiof), __FILE__, __LINE__); // "..\int\audiof.c", 209 } numAudiof++; } AudioFile* audioFile = &(audiof[index]); audioFile->flags = AUDIO_FILE_IN_USE; audioFile->fileHandle = (int)stream; if (compression == 2) { audioFile->flags |= AUDIO_FILE_COMPRESSED; audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); audioFile->fileSize *= 2; } else { audioFile->fileSize = filelength(fileno(stream)); } audioFile->position = 0; return index + 1; } // 0x41AAA0 int audiofCloseFile(int fileHandle) { AudioFile* audioFile = &(audiof[fileHandle - 1]); fclose((FILE*)audioFile->fileHandle); if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) { soundDecoderFree(audioFile->soundDecoder); } // Reset audio file (which also resets it's use flag). memset(audioFile, 0, sizeof(*audioFile)); return 0; } // 0x41AB08 int audiofRead(int fileHandle, void* buffer, unsigned int size) { AudioFile* ptr = &(audiof[fileHandle - 1]); int bytesRead; if ((ptr->flags & AUDIO_FILE_COMPRESSED) != 0) { bytesRead = soundDecoderDecode(ptr->soundDecoder, buffer, size); } else { bytesRead = fread(buffer, 1, size, (FILE*)ptr->fileHandle); } ptr->position += bytesRead; return bytesRead; } // 0x41AB74 long audiofSeek(int fileHandle, long offset, int origin) { void* buf; int remaining; int a4; AudioFile* audioFile = &(audiof[fileHandle - 1]); switch (origin) { case SEEK_SET: a4 = offset; break; case SEEK_CUR: a4 = audioFile->fileSize + offset; break; case SEEK_END: a4 = audioFile->position + offset; break; default: assert(false && "Should be unreachable"); } if ((audioFile->flags & AUDIO_FILE_COMPRESSED) != 0) { if (a4 <= audioFile->position) { soundDecoderFree(audioFile->soundDecoder); fseek((FILE*)audioFile->fileHandle, 0, 0); audioFile->soundDecoder = soundDecoderInit(decodeRead, audioFile->fileHandle, &(audioFile->field_14), &(audioFile->field_10), &(audioFile->fileSize)); audioFile->fileSize *= 2; audioFile->position = 0; if (a4) { buf = mymalloc(4096, __FILE__, __LINE__); // "..\int\audiof.c", 364 while (a4 > 4096) { audiofRead(fileHandle, buf, 4096); a4 -= 4096; } if (a4 != 0) { audiofRead(fileHandle, buf, a4); } myfree(buf, __FILE__, __LINE__); // "..\int\audiof.c", 370 } } else { buf = mymalloc(0x400, __FILE__, __LINE__); // "..\int\audiof.c", 316 remaining = audioFile->position - a4; while (remaining > 1024) { audiofRead(fileHandle, buf, 1024); remaining -= 1024; } if (remaining != 0) { audiofRead(fileHandle, buf, remaining); } // TODO: Obiously leaks memory. } return audioFile->position; } return fseek((FILE*)audioFile->fileHandle, offset, origin); } // 0x41AD20 long audiofFileSize(int fileHandle) { AudioFile* audioFile = &(audiof[fileHandle - 1]); return audioFile->fileSize; } // 0x41AD3C long audiofTell(int fileHandle) { AudioFile* audioFile = &(audiof[fileHandle - 1]); return audioFile->position; } // 0x41AD58 int audiofWrite(int fileHandle, const void* buffer, unsigned int size) { debug_printf("AudiofWrite shouldn't be ever called\n"); return 0; } // 0x41AD68 int initAudiof(AudioFileIsCompressedProc* isCompressedProc) { queryCompressedFunc = isCompressedProc; audiof = NULL; numAudiof = 0; return soundSetDefaultFileIO(audiofOpen, audiofCloseFile, audiofRead, audiofWrite, audiofSeek, audiofTell, audiofFileSize); } // 0x41ADAC void audiofClose() { if (audiof != NULL) { myfree(audiof, __FILE__, __LINE__); // "..\int\audiof.c", 405 } numAudiof = 0; audiof = NULL; } ================================================ FILE: src/int/audiof.h ================================================ #ifndef FALLOUT_INT_AUDIOF_H_ #define FALLOUT_INT_AUDIOF_H_ #include #include "sound_decoder.h" typedef enum AudioFileFlags { AUDIO_FILE_IN_USE = 0x01, AUDIO_FILE_COMPRESSED = 0x02, } AudioFileFlags; typedef struct AudioFile { int flags; int fileHandle; SoundDecoder* soundDecoder; int fileSize; int field_10; int field_14; int position; } AudioFile; typedef bool(AudioFileIsCompressedProc)(char* filePath); int audiofOpen(const char* fname, int flags, ...); int audiofCloseFile(int a1); int audiofRead(int a1, void* buf, unsigned int size); long audiofSeek(int handle, long offset, int origin); long audiofFileSize(int a1); long audiofTell(int a1); int audiofWrite(int handle, const void* buf, unsigned int size); int initAudiof(AudioFileIsCompressedProc* isCompressedProc); void audiofClose(); #endif /* FALLOUT_INT_AUDIOF_H_ */ ================================================ FILE: src/int/datafile.c ================================================ #include "int/datafile.h" #include #include "plib/color/color.h" #include "plib/db/db.h" #include "int/memdbg.h" #include "int/pcx.h" static char* defaultMangleName(char* path); // 0x5184AC static DatafileLoader* loadFunc = NULL; // 0x5184B0 static DatafileNameMangler* mangleName = defaultMangleName; // 0x56D7E0 static unsigned char pal[768]; // 0x42EE70 static char* defaultMangleName(char* path) { return path; } // NOTE: Unused. // // 0x42EE74 void datafileSetFilenameFunc(DatafileNameMangler* mangler) { mangleName = mangler; } // NOTE: Unused. // // 0x42EE7C void setBitmapLoadFunc(DatafileLoader* loader) { loadFunc = loader; } // 0x42EE84 void datafileConvertData(unsigned char* data, unsigned char* palette, int width, int height) { unsigned char indexedPalette[256]; indexedPalette[0] = 0; for (int index = 1; index < 256; index++) { // TODO: Check. int r = palette[index * 3 + 2] >> 3; int g = palette[index * 3 + 1] >> 3; int b = palette[index * 3] >> 3; int colorTableIndex = (r << 10) | (g << 5) | b; indexedPalette[index] = colorTable[colorTableIndex]; } int size = width * height; for (int index = 0; index < size; index++) { data[index] = indexedPalette[data[index]]; } } // NOTE: Unused. // // 0x42EEF8 void datafileConvertDataVGA(unsigned char* data, unsigned char* palette, int width, int height) { unsigned char indexedPalette[256]; indexedPalette[0] = 0; for (int index = 1; index < 256; index++) { // TODO: Check. int r = palette[index * 3 + 2] >> 1; int g = palette[index * 3 + 1] >> 1; int b = palette[index * 3] >> 1; int colorTableIndex = (r << 10) | (g << 5) | b; indexedPalette[index] = colorTable[colorTableIndex]; } int size = width * height; for (int index = 0; index < size; index++) { data[index] = indexedPalette[data[index]]; } } // 0x42EF60 unsigned char* loadRawDataFile(char* path, int* widthPtr, int* heightPtr) { char* mangledPath = mangleName(path); char* dot = strrchr(mangledPath, '.'); if (dot != NULL) { if (stricmp(dot + 1, "pcx") == 0) { return loadPCX(mangledPath, widthPtr, heightPtr, pal); } } if (loadFunc != NULL) { return loadFunc(mangledPath, pal, widthPtr, heightPtr); } return NULL; } // 0x42EFCC unsigned char* loadDataFile(char* path, int* widthPtr, int* heightPtr) { unsigned char* v1 = loadRawDataFile(path, widthPtr, heightPtr); if (v1 != NULL) { datafileConvertData(v1, pal, *widthPtr, *heightPtr); } return v1; } // NOTE: Unused // // 0x42EFF4 unsigned char* load256Palette(char* path) { int width; int height; unsigned char* v3 = loadRawDataFile(path, &width, &height); if (v3 != NULL) { myfree(v3, __FILE__, __LINE__); // "..\\int\\DATAFILE.C", 148 return pal; } return NULL; } // NOTE: Unused. // // 0x42F024 void trimBuffer(unsigned char* data, int* widthPtr, int* heightPtr) { int width = *widthPtr; int height = *heightPtr; unsigned char* temp = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // "..\\int\\DATAFILE.C", 157 // NOTE: Original code does not initialize `x`. int y = 0; int x = 0; unsigned char* src1 = data; for (y = 0; y < height; y++) { if (*src1 == 0) { break; } unsigned char* src2 = src1; for (x = 0; x < width; x++) { if (*src2 == 0) { break; } *temp++ = *src2++; } src1 += width; } memcpy(data, temp, x * y); myfree(temp, __FILE__, __LINE__); // // "..\\int\\DATAFILE.C", 171 } // 0x42F0E4 unsigned char* datafileGetPalette() { return pal; } // NOTE: Unused. // // 0x42F0EC unsigned char* datafileLoadBlock(char* path, int* sizePtr) { const char* mangledPath = mangleName(path); File* stream = db_fopen(mangledPath, "rb"); if (stream == NULL) { return NULL; } int size = db_filelength(stream); void* data = mymalloc(size, __FILE__, __LINE__); // "..\\int\\DATAFILE.C", 185 if (data == NULL) { // NOTE: This code is unreachable, mymalloc never fails. // Otherwise it leaks stream. *sizePtr = 0; return NULL; } db_fread(data, 1, size, stream); db_fclose(stream); *sizePtr = size; return data; } ================================================ FILE: src/int/datafile.h ================================================ #ifndef FALLOUT_INT_DATAFILE_H_ #define FALLOUT_INT_DATAFILE_H_ typedef unsigned char*(DatafileLoader)(char* path, unsigned char* palette, int* widthPtr, int* heightPtr); typedef char*(DatafileNameMangler)(char* path); void datafileSetFilenameFunc(DatafileNameMangler* mangler); void setBitmapLoadFunc(DatafileLoader* loader); void datafileConvertData(unsigned char* data, unsigned char* palette, int width, int height); void datafileConvertDataVGA(unsigned char* data, unsigned char* palette, int width, int height); unsigned char* loadRawDataFile(char* path, int* widthPtr, int* heightPtr); unsigned char* loadDataFile(char* path, int* widthPtr, int* heightPtr); unsigned char* load256Palette(char* path); void trimBuffer(unsigned char* data, int* widthPtr, int* heightPtr); unsigned char* datafileGetPalette(); unsigned char* datafileLoadBlock(char* path, int* sizePtr); #endif /* FALLOUT_INT_DATAFILE_H_ */ ================================================ FILE: src/int/dialog.c ================================================ #include "int/dialog.h" #include #include "int/window.h" #include "plib/gnw/input.h" #include "int/memdbg.h" #include "int/movie.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" typedef struct STRUCT_56DAE0_FIELD_4_FIELD_C { char* field_0; union { int proc; char* string; }; int kind; int field_C; int field_10; int field_14; short field_18; short field_1A; } STRUCT_56DAE0_FIELD_4_FIELD_C; typedef struct STRUCT_56DAE0_FIELD_4 { void* field_0; char* field_4; void* field_8; STRUCT_56DAE0_FIELD_4_FIELD_C* field_C; int field_10; int field_14; int field_18; // probably font number } STRUCT_56DAE0_FIELD_4; typedef struct STRUCT_56DAE0 { Program* field_0; STRUCT_56DAE0_FIELD_4* field_4; int field_8; int field_C; int field_10; int field_14; int field_18; } STRUCT_56DAE0; typedef struct DialogWindowData { short flags; int width; int height; int x; int y; char* backgroundFileName; } DialogWindowData; // NOTE: There are |upButton| and |downButton| variables which are definitely // instances of some struct. The values are set only via scripting functions, // however they are never read back. It's impossible to understand the meaning // of the fields and give them names. I've commented my guesses based on // intuition. typedef struct DialogScrollButtonData { int field_0; // x int field_4; // y int field_8; // flags char* field_C; // normal image file name char* field_10; // pressed image file name char* field_14; // hover image file name char* field_18; // mask or disabled image file name } DialogScrollButtonData; static STRUCT_56DAE0_FIELD_4* getReply(); static void replyAddOption(const char* a1, const char* a2, int a3); static void replyAddOptionProc(const char* a1, int a2, int a3); static void optionFree(STRUCT_56DAE0_FIELD_4_FIELD_C* a1); static void replyFree(); static int endDialog(); static void printLine(int win, char** strings, int strings_num, int a4, int a5, int a6, int a7, int a8, int a9); static void printStr(int win, char* a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); static int abortReply(int a1); static void endReply(); static void drawStr(int win, char* a2, int font, int width, int height, int left, int top, int a8, int a9, int a10); // 0x5184B4 static int tods = -1; // 0x5184B8 static int topDialogLine = 0; // 0x5184BC static int topDialogReply = 0; // 0x5184E4 DialogWinDrawCallback* replyWinDrawCallback = NULL; // 0x5184E8 DialogWinDrawCallback* optionsWinDrawCallback = NULL; // 0x5184EC static int defaultBorderX = 7; // 0x5184F0 static int defaultBorderY = 7; // 0x5184F4 static int defaultSpacing = 5; // 0x5184F8 static int replyRGBset = 0; // 0x5184FC static int optionRGBset = 0; // 0x518500 static int exitDialog = 0; // 0x518504 static int inDialog = 0; // 0x518508 static int mediaFlag = 2; // 0x56DAE0 static STRUCT_56DAE0 dialog[4]; // 0x56DB60 static DialogWindowData defaultOption; // 0x56DB78 static DialogWindowData defaultReply; // 0x56DB90 static int replyPlaying; // 0x56DB94 static int replyWin = -1; // 0x56DB98 static int replyG; // 0x56DB9C static int replyB; // 0x56DBA4 static int optionG; // 0x56DBA8 static int replyR; // 0x56DBAC static int optionB; // 0x56DBB0 static int optionR; // 0x56DBB4 static DialogScrollButtonData downButton; // 0x56DBD0 static char* replyTitleDefault; // 0x56DBD4 static DialogScrollButtonData upButton; // 0x42F434 static STRUCT_56DAE0_FIELD_4* getReply() { STRUCT_56DAE0_FIELD_4* v0; STRUCT_56DAE0_FIELD_4_FIELD_C* v1; v0 = &(dialog[tods].field_4[dialog[tods].field_C]); if (v0->field_C == NULL) { v0->field_14 = 1; v1 = (STRUCT_56DAE0_FIELD_4_FIELD_C*)mymalloc(sizeof(STRUCT_56DAE0_FIELD_4_FIELD_C), __FILE__, __LINE__); // "..\\int\\DIALOG.C", 789 } else { v0->field_14++; v1 = (STRUCT_56DAE0_FIELD_4_FIELD_C*)myrealloc(v0->field_C, sizeof(STRUCT_56DAE0_FIELD_4_FIELD_C) * v0->field_14, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 793 } v0->field_C = v1; return v0; } // 0x42F4C0 static void replyAddOption(const char* a1, const char* a2, int a3) { STRUCT_56DAE0_FIELD_4* v18; int v17; char* v14; char* v15; v18 = getReply(); v17 = v18->field_14 - 1; v18->field_C[v17].kind = 2; if (a1 != NULL) { v14 = (char*)mymalloc(strlen(a1) + 1, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 805 strcpy(v14, a1); v18->field_C[v17].field_0 = v14; } else { v18->field_C[v17].field_0 = NULL; } if (a2 != NULL) { v15 = (char*)mymalloc(strlen(a2) + 1, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 810 strcpy(v15, a2); v18->field_C[v17].string = v15; } else { v18->field_C[v17].string = NULL; } v18->field_C[v17].field_18 = windowGetFont(); v18->field_C[v17].field_1A = defaultOption.flags; v18->field_C[v17].field_14 = a3; } // 0x42F624 static void replyAddOptionProc(const char* a1, int a2, int a3) { STRUCT_56DAE0_FIELD_4* v5; int v13; char* v11; v5 = getReply(); v13 = v5->field_14 - 1; v5->field_C[v13].kind = 1; if (a1 != NULL) { v11 = (char*)mymalloc(strlen(a1) + 1, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 830 strcpy(v11, a1); v5->field_C[v13].field_0 = v11; } else { v5->field_C[v13].field_0 = NULL; } v5->field_C[v13].proc = a2; v5->field_C[v13].field_18 = windowGetFont(); v5->field_C[v13].field_1A = defaultOption.flags; v5->field_C[v13].field_14 = a3; } // 0x42F714 static void optionFree(STRUCT_56DAE0_FIELD_4_FIELD_C* a1) { if (a1->field_0 != NULL) { myfree(a1->field_0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 844 } if (a1->kind == 2) { if (a1->string != NULL) { myfree(a1->string, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 846 } } } // 0x42F754 static void replyFree() { int i; int j; STRUCT_56DAE0* ptr; STRUCT_56DAE0_FIELD_4* v6; ptr = &(dialog[tods]); for (i = 0; i < ptr->field_8; i++) { v6 = &(dialog[tods].field_4[i]); if (v6->field_C != NULL) { for (j = 0; j < v6->field_14; j++) { optionFree(&(v6->field_C[j])); } myfree(v6->field_C, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 857 } if (v6->field_8 != NULL) { myfree(v6->field_8, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 860 } if (v6->field_4 != NULL) { myfree(v6->field_4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 862 } if (v6->field_0 != NULL) { myfree(v6->field_0, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 864 } } if (ptr->field_4 != NULL) { myfree(ptr->field_4, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 867 } } // 0x42FB94 static int endDialog() { if (tods == -1) { return -1; } topDialogReply = dialog[tods].field_10; replyFree(); if (replyTitleDefault != NULL) { myfree(replyTitleDefault, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 986 replyTitleDefault = NULL; } --tods; return 0; } // 0x42FC70 static void printLine(int win, char** strings, int strings_num, int a4, int a5, int a6, int a7, int a8, int a9) { int i; int v11; for (i = 0; i < strings_num; i++) { v11 = a7 + i * text_height(); windowPrintBuf(win, strings[i], strlen(strings[i]), a4, a5 + a7, a6, v11, a8, a9); } } // 0x42FCF0 static void printStr(int win, char* a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { char** strings; int strings_num; strings = windowWordWrap(a2, a3, 0, &strings_num); printLine(win, strings, strings_num, a3, a4, a5, a6, a7, a8); windowFreeWordList(strings, strings_num); } // 0x430104 static int abortReply(int a1) { int result; int y; int x; if (replyPlaying == 2) { return moviePlaying() == 0; } else if (replyPlaying == 3) { return 1; } result = 1; if (a1) { if (replyWin != -1) { if (!(mouse_get_buttons() & 0x10)) { result = 0; } else { mouse_get_position(&x, &y); if (win_get_top_win(x, y) != replyWin) { result = 0; } } } } return result; } // 0x430180 static void endReply() { if (replyPlaying != 2) { if (replyPlaying == 1) { if (!(mediaFlag & 2) && replyWin != -1) { win_delete(replyWin); replyWin = -1; } } else if (replyPlaying != 3 && replyWin != -1) { win_delete(replyWin); replyWin = -1; } } } // 0x4301E8 static void drawStr(int win, char* str, int font, int width, int height, int left, int top, int a8, int a9, int a10) { int old_font; Rect rect; old_font = windowGetFont(); windowSetFont(font); printStr(win, str, width, height, left, top, a8, a9, a10); rect.ulx = left; rect.uly = top; rect.lrx = width + left; rect.lry = height + top; win_draw_rect(win, &rect); windowSetFont(old_font); } // 0x430D40 int dialogStart(Program* a1) { STRUCT_56DAE0* ptr; if (tods == 3) { return 1; } ptr = &(dialog[tods]); ptr->field_0 = a1; ptr->field_4 = 0; ptr->field_8 = 0; ptr->field_C = -1; ptr->field_10 = -1; ptr->field_14 = 1; ptr->field_10 = 1; tods++; return 0; } // 0x430DB8 int dialogRestart() { if (tods == -1) { return 1; } dialog[tods].field_10 = 0; return 0; } // 0x430DE4 int dialogGotoReply(const char* a1) { STRUCT_56DAE0* ptr; STRUCT_56DAE0_FIELD_4* v5; int i; if (tods == -1) { return 1; } if (a1 != NULL) { ptr = &(dialog[tods]); for (i = 0; i < ptr->field_8; i++) { v5 = &(ptr->field_4[i]); if (v5->field_4 != NULL && stricmp(v5->field_4, a1) == 0) { ptr->field_10 = i; return 0; } } return 1; } dialog[tods].field_10 = 0; return 0; } // 0x430E84 int dialogTitle(const char* a1) { if (replyTitleDefault != NULL) { myfree(replyTitleDefault, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2561 } if (a1 != NULL) { replyTitleDefault = (char*)mymalloc(strlen(a1) + 1, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2564 strcpy(replyTitleDefault, a1); } else { replyTitleDefault = NULL; } return 0; } // 0x430EFC int dialogReply(const char* a1, const char* a2) { // TODO: Incomplete. // _replyAddNew(a1, a2); return 0; } // 0x430F04 int dialogOption(const char* a1, const char* a2) { if (dialog[tods].field_C == -1) { return 0; } replyAddOption(a1, a2, 0); return 0; } // 0x430F38 int dialogOptionProc(const char* a1, int a2) { if (dialog[tods].field_C == -1) { return 1; } replyAddOptionProc(a1, a2, 0); return 0; } // 0x430FD4 int dialogMessage(const char* a1, const char* a2, int timeout) { // TODO: Incomplete. return -1; } // 0x431088 int dialogGo(int a1) { // TODO: Incomplete. return -1; } // 0x431184 int dialogGetExitPoint() { return topDialogLine + (topDialogReply << 16); } // 0x431198 int dialogQuit() { if (inDialog) { exitDialog = 1; } else { endDialog(); } return 0; } // 0x4311B8 int dialogSetOptionWindow(int x, int y, int width, int height, char* backgroundFileName) { defaultOption.x = x; defaultOption.y = y; defaultOption.width = width; defaultOption.height = height; defaultOption.backgroundFileName = backgroundFileName; return 0; } // 0x4311E0 int dialogSetReplyWindow(int x, int y, int width, int height, char* backgroundFileName) { defaultReply.x = x; defaultReply.y = y; defaultReply.width = width; defaultReply.height = height; defaultReply.backgroundFileName = backgroundFileName; return 0; } // 0x431208 int dialogSetBorder(int a1, int a2) { defaultBorderX = a1; defaultBorderY = a2; return 0; } // 0x431218 int dialogSetScrollUp(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7) { upButton.field_0 = a1; upButton.field_4 = a2; if (upButton.field_C != NULL) { myfree(upButton.field_C, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2750 } upButton.field_C = a3; if (upButton.field_10 != NULL) { myfree(upButton.field_10, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2752 } upButton.field_10 = a4; if (upButton.field_14 != NULL) { myfree(upButton.field_14, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2754 } upButton.field_14 = a5; if (upButton.field_18 != NULL) { myfree(upButton.field_18, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2756 } upButton.field_18 = a6; upButton.field_8 = a7; return 0; } // 0x4312C0 int dialogSetScrollDown(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7) { downButton.field_0 = a1; downButton.field_4 = a2; if (downButton.field_C != NULL) { myfree(downButton.field_C, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2765 } downButton.field_C = a3; if (downButton.field_10 != NULL) { myfree(downButton.field_10, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2767 } downButton.field_10 = a4; if (downButton.field_14 != NULL) { myfree(downButton.field_14, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2769 } downButton.field_14 = a5; if (downButton.field_18 != NULL) { myfree(downButton.field_18, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2771 } downButton.field_18 = a6; downButton.field_8 = a7; return 0; } // 0x431368 int dialogSetSpacing(int value) { defaultSpacing = value; return 0; } // 0x431370 int dialogSetOptionColor(float a1, float a2, float a3) { optionR = (int)(a1 * 31.0); optionG = (int)(a2 * 31.0); optionB = (int)(a3 * 31.0); optionRGBset = 1; return 0; } // 0x4313C8 int dialogSetReplyColor(float a1, float a2, float a3) { replyR = (int)(a1 * 31.0); replyG = (int)(a2 * 31.0); replyB = (int)(a3 * 31.0); replyRGBset = 1; return 0; } // 0x431420 int dialogSetOptionFlags(short flags) { defaultOption.flags = flags; return 1; } // 0x431420 int dialogSetReplyFlags(short flags) { // FIXME: Obvious error, flags should be set on |defaultReply|. defaultOption.flags = flags; return 1; } // 0x431430 void initDialog() { } // 0x431434 void dialogClose() { if (upButton.field_C) { myfree(upButton.field_C, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2818 } if (upButton.field_10) { myfree(upButton.field_10, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2819 } if (upButton.field_14) { myfree(upButton.field_14, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2820 } if (upButton.field_18) { myfree(upButton.field_18, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2821 } if (downButton.field_C) { myfree(downButton.field_C, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2823 } if (downButton.field_10) { myfree(downButton.field_10, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2824 } if (downButton.field_14) { myfree(downButton.field_14, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2825 } if (downButton.field_18) { myfree(downButton.field_18, __FILE__, __LINE__); // "..\\int\\DIALOG.C", 2826 } } // 0x431518 int dialogGetDialogDepth() { return tods; } // 0x431520 void dialogRegisterWinDrawCallbacks(DialogWinDrawCallback* reply, DialogWinDrawCallback* options) { replyWinDrawCallback = reply; optionsWinDrawCallback = options; } // 0x431530 int dialogToggleMediaFlag(int a1) { if ((a1 & mediaFlag) == a1) { mediaFlag &= ~a1; } else { mediaFlag |= a1; } return mediaFlag; } // 0x431554 int dialogGetMediaFlag() { return mediaFlag; } ================================================ FILE: src/int/dialog.h ================================================ #ifndef FALLOUT_INT_DIALOG_H_ #define FALLOUT_INT_DIALOG_H_ #include "int/intrpret.h" typedef void DialogWinDrawCallback(int win); extern DialogWinDrawCallback* replyWinDrawCallback; extern DialogWinDrawCallback* optionsWinDrawCallback; int dialogStart(Program* a1); int dialogRestart(); int dialogGotoReply(const char* a1); int dialogTitle(const char* a1); int dialogReply(const char* a1, const char* a2); int dialogOption(const char* a1, const char* a2); int dialogOptionProc(const char* a1, int a2); int dialogMessage(const char* a1, const char* a2, int timeout); int dialogGo(int a1); int dialogGetExitPoint(); int dialogQuit(); int dialogSetOptionWindow(int x, int y, int width, int height, char* backgroundFileName); int dialogSetReplyWindow(int x, int y, int width, int height, char* backgroundFileName); int dialogSetBorder(int a1, int a2); int dialogSetScrollUp(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7); int dialogSetScrollDown(int a1, int a2, char* a3, char* a4, char* a5, char* a6, int a7); int dialogSetSpacing(int value); int dialogSetOptionColor(float a1, float a2, float a3); int dialogSetReplyColor(float a1, float a2, float a3); int dialogSetOptionFlags(short flags); int dialogSetReplyFlags(short flags); void initDialog(); void dialogClose(); int dialogGetDialogDepth(); void dialogRegisterWinDrawCallbacks(DialogWinDrawCallback* reply, DialogWinDrawCallback* options); int dialogToggleMediaFlag(int a1); int dialogGetMediaFlag(); #endif /* FALLOUT_INT_DIALOG_H_ */ ================================================ FILE: src/int/export.c ================================================ #include "int/export.h" #include #include #include "int/intlib.h" #include "int/memdbg.h" typedef struct ExternalVariable { char name[32]; char* programName; opcode_t type; union { int value; char* stringValue; }; } ExternalVariable; typedef struct ExternalProcedure { char name[32]; Program* program; int argumentCount; int address; } ExternalProcedure; static unsigned int hashName(const char* identifier); static ExternalProcedure* findProc(const char* identifier); static ExternalProcedure* findEmptyProc(const char* identifier); static ExternalVariable* findVar(const char* identifier); static ExternalVariable* findEmptyVar(const char* identifier); static void removeProgramReferences(Program* program); // 0x570C00 static ExternalProcedure procHashTable[1013]; // 0x57BA1C static ExternalVariable varHashTable[1013]; // NOTE: Inlined. // // 0x440F10 static unsigned int hashName(const char* identifier) { unsigned int v1 = 0; const char* pch = identifier; while (*pch != '\0') { int ch = *pch & 0xFF; v1 += (tolower(ch) & 0xFF) + (v1 * 8) + (v1 >> 29); pch++; } v1 = v1 % 1013; return v1; } // 0x440F58 static ExternalProcedure* findProc(const char* identifier) { // NOTE: Uninline. unsigned int v1 = hashName(identifier); unsigned int v2 = v1; ExternalProcedure* externalProcedure = &(procHashTable[v1]); if (externalProcedure->program != NULL) { if (stricmp(externalProcedure->name, identifier) == 0) { return externalProcedure; } } do { v1 += 7; if (v1 >= 1013) { v1 -= 1013; } externalProcedure = &(procHashTable[v1]); if (externalProcedure->program != NULL) { if (stricmp(externalProcedure->name, identifier) == 0) { return externalProcedure; } } } while (v1 != v2); return NULL; } // 0x441018 static ExternalProcedure* findEmptyProc(const char* identifier) { // NOTE: Uninline. unsigned int v1 = hashName(identifier); unsigned int a2 = v1; ExternalProcedure* externalProcedure = &(procHashTable[v1]); if (externalProcedure->name[0] == '\0') { return externalProcedure; } do { v1 += 7; if (v1 >= 1013) { v1 -= 1013; } externalProcedure = &(procHashTable[v1]); if (externalProcedure->name[0] == '\0') { return externalProcedure; } } while (v1 != a2); return NULL; } // 0x4410AC static ExternalVariable* findVar(const char* identifier) { // NOTE: Uninline. unsigned int v1 = hashName(identifier); unsigned int v2 = v1; ExternalVariable* exportedVariable = &(varHashTable[v1]); if (stricmp(exportedVariable->name, identifier) == 0) { return exportedVariable; } do { exportedVariable = &(varHashTable[v1]); if (exportedVariable->name[0] == '\0') { break; } v1 += 7; if (v1 >= 1013) { v1 -= 1013; } exportedVariable = &(varHashTable[v1]); if (stricmp(exportedVariable->name, identifier) == 0) { return exportedVariable; } } while (v1 != v2); return NULL; } // NOTE: Unused. // // 0x441164 int exportGetVariable(const char* identifier, opcode_t* typePtr, int* valuePtr) { ExternalVariable* variable; variable = findVar(identifier); if (variable != NULL) { *typePtr = variable->type; *valuePtr = variable->value; return 1; } *typePtr = 0; *valuePtr = 0; return 0; } // 0x44118C static ExternalVariable* findEmptyVar(const char* identifier) { // NOTE: Uninline. unsigned int v1 = hashName(identifier); unsigned int v2 = v1; ExternalVariable* exportedVariable = &(varHashTable[v1]); if (exportedVariable->name[0] == '\0') { return exportedVariable; } do { v1 += 7; if (v1 >= 1013) { v1 -= 1013; } exportedVariable = &(varHashTable[v1]); if (exportedVariable->name[0] == '\0') { return exportedVariable; } } while (v1 != v2); return NULL; } // NOTE: Unused. // // 0x441220 int exportStoreStringVariable(const char* identifier, const char* value) { ExternalVariable* variable; variable = findVar(identifier); if (variable != NULL) { if ((variable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { myfree(variable->stringValue, __FILE__, __LINE__); // "..\int\EXPORT.C", 155 } variable->type = VALUE_TYPE_DYNAMIC_STRING; variable->stringValue = mystrdup(value, __FILE__, __LINE__); // "..\int\EXPORT.C", 159 return 0; } return 1; } // 0x44127C int exportStoreVariable(Program* program, const char* name, opcode_t opcode, int data) { ExternalVariable* exportedVariable = findVar(name); if (exportedVariable == NULL) { return 1; } if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { myfree(exportedVariable->stringValue, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 169 } if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { if (program != NULL) { const char* stringValue = interpretGetString(program, opcode, data); exportedVariable->type = VALUE_TYPE_DYNAMIC_STRING; exportedVariable->stringValue = (char*)mymalloc(strlen(stringValue) + 1, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 175 strcpy(exportedVariable->stringValue, stringValue); } } else { exportedVariable->value = data; exportedVariable->type = opcode; } return 0; } // NOTE: Unused. // // 0x441330 int exportStoreVariableByTag(const char* identifier, opcode_t type, int value) { ExternalVariable* variable; variable = findVar(identifier); if (variable != NULL) { if ((variable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { myfree(variable->stringValue, __FILE__, __LINE__); // "..\int\EXPORT.C", 191 } if ((type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { variable->type = VALUE_TYPE_DYNAMIC_STRING; variable->stringValue = (char*)mymalloc(strlen((char*)value) + 1, __FILE__, __LINE__); // "..\int\EXPORT.C", 196 strcpy(variable->stringValue, (char*)value); } else { variable->value = value; variable->type = type; } return 0; } return 1; } // 0x4413D4 int exportFetchVariable(Program* program, const char* name, opcode_t* opcodePtr, int* dataPtr) { ExternalVariable* exportedVariable = findVar(name); if (exportedVariable == NULL) { return 1; } *opcodePtr = exportedVariable->type; if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { *dataPtr = interpretAddString(program, exportedVariable->stringValue); } else { *dataPtr = exportedVariable->value; } return 0; } // 0x4414B8 int exportExportVariable(Program* program, const char* identifier) { const char* programName = program->name; ExternalVariable* exportedVariable = findVar(identifier); if (exportedVariable != NULL) { if (stricmp(exportedVariable->programName, programName) != 0) { return 1; } if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { myfree(exportedVariable->stringValue, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 234 } } else { exportedVariable = findEmptyVar(identifier); if (exportedVariable == NULL) { return 1; } strncpy(exportedVariable->name, identifier, 31); exportedVariable->programName = (char*)mymalloc(strlen(programName) + 1, __FILE__, __LINE__); // // "..\\int\\EXPORT.C", 243 strcpy(exportedVariable->programName, programName); } exportedVariable->type = VALUE_TYPE_INT; exportedVariable->value = 0; return 0; } // 0x4414FC static void removeProgramReferences(Program* program) { for (int index = 0; index < 1013; index++) { ExternalProcedure* externalProcedure = &(procHashTable[index]); if (externalProcedure->program == program) { externalProcedure->name[0] = '\0'; externalProcedure->program = NULL; } } } // 0x44152C void initExport() { interpretRegisterProgramDeleteCallback(removeProgramReferences); } // 0x441538 void exportClose() { for (int index = 0; index < 1013; index++) { ExternalVariable* exportedVariable = &(varHashTable[index]); if (exportedVariable->name[0] != '\0') { myfree(exportedVariable->programName, __FILE__, __LINE__); // ..\\int\\EXPORT.C, 274 } if (exportedVariable->type == VALUE_TYPE_DYNAMIC_STRING) { myfree(exportedVariable->stringValue, __FILE__, __LINE__); // ..\\int\\EXPORT.C, 276 } } } // 0x44158C Program* exportFindProcedure(const char* identifier, int* addressPtr, int* argumentCountPtr) { ExternalProcedure* externalProcedure = findProc(identifier); if (externalProcedure == NULL) { return NULL; } if (externalProcedure->program == NULL) { return NULL; } *addressPtr = externalProcedure->address; *argumentCountPtr = externalProcedure->argumentCount; return externalProcedure->program; } // 0x4415B0 int exportExportProcedure(Program* program, const char* identifier, int address, int argumentCount) { ExternalProcedure* externalProcedure = findProc(identifier); if (externalProcedure != NULL) { if (program != externalProcedure->program) { return 1; } } else { externalProcedure = findEmptyProc(identifier); if (externalProcedure == NULL) { return 1; } strncpy(externalProcedure->name, identifier, 31); } externalProcedure->argumentCount = argumentCount; externalProcedure->address = address; externalProcedure->program = program; return 0; } // 0x441824 void exportClearAllVariables() { for (int index = 0; index < 1013; index++) { ExternalVariable* exportedVariable = &(varHashTable[index]); if (exportedVariable->name[0] != '\0') { if ((exportedVariable->type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { if (exportedVariable->stringValue != NULL) { myfree(exportedVariable->stringValue, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 387 } } if (exportedVariable->programName != NULL) { myfree(exportedVariable->programName, __FILE__, __LINE__); // "..\\int\\EXPORT.C", 393 exportedVariable->programName = NULL; } exportedVariable->name[0] = '\0'; exportedVariable->type = 0; } } } ================================================ FILE: src/int/export.h ================================================ #ifndef FALLOUT_INT_EXPORT_H_ #define FALLOUT_INT_EXPORT_H_ #include "int/intrpret.h" int exportGetVariable(const char* identifier, opcode_t* typePtr, int* valuePtr); int exportStoreStringVariable(const char* identifier, const char* value); int exportStoreVariable(Program* program, const char* identifier, opcode_t opcode, int data); int exportStoreVariableByTag(const char* identifier, opcode_t type, int value); int exportFetchVariable(Program* program, const char* name, opcode_t* opcodePtr, int* dataPtr); int exportExportVariable(Program* program, const char* identifier); void initExport(); void exportClose(); Program* exportFindProcedure(const char* identifier, int* addressPtr, int* argumentCountPtr); int exportExportProcedure(Program* program, const char* identifier, int address, int argumentCount); void exportClearAllVariables(); #endif /* FALLOUT_INT_EXPORT_H_ */ ================================================ FILE: src/int/intlib.c ================================================ #include "int/intlib.h" #include #include "int/window.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "int/datafile.h" #include "plib/gnw/debug.h" #include "int/dialog.h" #include "int/support/intextra.h" #include "int/memdbg.h" #include "int/mousemgr.h" #include "int/nevs.h" #include "int/share1.h" #include "int/sound.h" #include "plib/gnw/text.h" #include "plib/gnw/intrface.h" #define INT_LIB_SOUNDS_CAPACITY 32 #define INT_LIB_KEY_HANDLERS_CAPACITY 256 typedef struct IntLibKeyHandlerEntry { Program* program; int proc; } IntLibKeyHandlerEntry; static void op_fillwin3x3(Program* program); static void op_format(Program* program); static void op_print(Program* program); static void op_selectfilelist(Program* program); static void op_tokenize(Program* program); static void op_printrect(Program* program); static void op_selectwin(Program* program); static void op_display(Program* program); static void op_displayraw(Program* program); static void interpretFadePaletteBK(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration, int shouldProcessBk); static void op_fadein(Program* program); static void op_fadeout(Program* program); static void op_movieflags(Program* program); static void op_playmovie(Program* program); static void op_playmovierect(Program* program); static void op_stopmovie(Program* program); static void op_addregionproc(Program* program); static void op_addregionrightproc(Program* program); static void op_createwin(Program* program); static void op_resizewin(Program* program); static void op_scalewin(Program* program); static void op_deletewin(Program* program); static void op_saystart(Program* program); static void op_deleteregion(Program* program); static void op_activateregion(Program* program); static void op_checkregion(Program* program); static void op_addregion(Program* program); static void op_saystartpos(Program* program); static void op_sayreplytitle(Program* program); static void op_saygotoreply(Program* program); static void op_sayreply(Program* program); static void op_sayoption(Program* program); static int checkDialog(Program* program); static void op_sayend(Program* program); static void op_saygetlastpos(Program* program); static void op_sayquit(Program* program); static void op_saymessagetimeout(Program* program); static void op_saymessage(Program* program); static void op_gotoxy(Program* program); static void op_addbuttonflag(Program* program); static void op_addregionflag(Program* program); static void op_addbutton(Program* program); static void op_addbuttontext(Program* program); static void op_addbuttongfx(Program* program); static void op_addbuttonproc(Program* program); static void op_addbuttonrightproc(Program* program); static void op_showwin(Program* program); static void op_deletebutton(Program* program); static void op_fillwin(Program* program); static void op_fillrect(Program* program); static void op_hidemouse(Program* program); static void op_showmouse(Program* program); static void op_mouseshape(Program* program); static void op_setglobalmousefunc(Program* Program); static void op_displaygfx(Program* program); static void op_loadpalettetable(Program* program); static void op_addNamedEvent(Program* program); static void op_addNamedHandler(Program* program); static void op_clearNamed(Program* program); static void op_signalNamed(Program* program); static void op_addkey(Program* program); static void op_deletekey(Program* program); static void op_refreshmouse(Program* program); static void op_setfont(Program* program); static void op_settextflags(Program* program); static void op_settextcolor(Program* program); static void op_sayoptioncolor(Program* program); static void op_sayreplycolor(Program* program); static void op_sethighlightcolor(Program* program); static void op_sayreplywindow(Program* program); static void op_sayreplyflags(Program* program); static void op_sayoptionflags(Program* program); static void op_sayoptionwindow(Program* program); static void op_sayborder(Program* program); static void op_sayscrollup(Program* program); static void op_sayscrolldown(Program* program); static void op_saysetspacing(Program* program); static void op_sayrestart(Program* program); static void soundCallbackInterpret(void* userData, int a2); static int soundDeleteInterpret(int value); static int soundPauseInterpret(int value); static int soundRewindInterpret(int value); static int soundUnpauseInterpret(int value); static void op_soundplay(Program* program); static void op_soundpause(Program* program); static void op_soundresume(Program* program); static void op_soundstop(Program* program); static void op_soundrewind(Program* program); static void op_sounddelete(Program* program); static void op_setoneoptpause(Program* program); static bool intLibDoInput(int key); // 0x519038 static int TimeOut = 0; // 0x59D5D0 static Sound* interpretSounds[INT_LIB_SOUNDS_CAPACITY]; // 0x59D650 static unsigned char blackPal[256 * 3]; // 0x59D950 static IntLibKeyHandlerEntry inputProc[INT_LIB_KEY_HANDLERS_CAPACITY]; // 0x59E150 static bool currentlyFadedIn; // 0x59E154 static int anyKeyOffset; // 0x59E158 static int numCallbacks; // 0x59E15C static Program* anyKeyProg; // 0x59E160 static IntLibProgramDeleteCallback** callbacks; // 0x59E164 static int sayStartingPosition; // 0x461780 static void op_fillwin3x3(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to fillwin3x3"); } char* fileName = interpretGetString(program, opcode, data); char* mangledFileName = interpretMangleName(fileName); int imageWidth; int imageHeight; unsigned char* imageData = loadDataFile(mangledFileName, &imageWidth, &imageHeight); if (imageData == NULL) { interpretError("cannot load 3x3 file '%s'", mangledFileName); } selectWindowID(program->windowId); alphaBltBufRect(imageData, imageWidth, imageHeight, windowGetBuffer(), windowWidth(), windowHeight()); myfree(imageData, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 94 } // 0x461850 static void op_format(Program* program) { opcode_t opcode[6]; int data[6]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 6; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 6 given to format\n"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 5 given to format\n"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 4 given to format\n"); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 3 given to format\n"); } if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to format\n"); } if ((opcode[5] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid arg 1 given to format\n"); } char* string = interpretGetString(program, opcode[5], data[5]); int x = data[4]; int y = data[3]; int width = data[2]; int height = data[1]; int textAlignment = data[0]; if (!windowFormatMessage(string, x, y, width, height, textAlignment)) { interpretError("Error formatting message\n"); } } // 0x461A5C static void op_print(Program* program) { selectWindowID(program->windowId); opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } switch (opcode & VALUE_TYPE_MASK) { case VALUE_TYPE_STRING: interpretOutput("%s", interpretGetString(program, opcode, data)); break; case VALUE_TYPE_FLOAT: interpretOutput("%.5f", *((float*)&data)); break; case VALUE_TYPE_INT: interpretOutput("%d", data); break; } } // 0x461B10 static void op_selectfilelist(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Error, invalid arg 2 given to selectfilelist"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Error, invalid arg 1 given to selectfilelist"); } char* pattern = interpretGetString(program, opcode[0], data[0]); char* title = interpretGetString(program, opcode[1], data[1]); int fileListLength; char** fileList = getFileList(interpretMangleName(pattern), &fileListLength); if (fileList != NULL && fileListLength != 0) { int selectedIndex = win_list_select(title, fileList, fileListLength, NULL, 320 - text_width(title) / 2, 200, colorTable[0x7FFF] | 0x10000); if (selectedIndex != -1) { interpretPushLong(program, interpretAddString(program, fileList[selectedIndex])); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } else { interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } freeFileList(fileList); } else { interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x461CA0 static void op_tokenize(Program* program) { opcode_t opcode[3]; int data[3]; opcode[0] = interpretPopShort(program); data[0] = interpretPopLong(program); if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[0], data[0]); } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Error, invalid arg 3 to tokenize."); } opcode[1] = interpretPopShort(program); data[1] = interpretPopLong(program); if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[1], data[1]); } char* prev = NULL; if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[1] != 0) { interpretError("Error, invalid arg 2 to tokenize. (only accept 0 for int value)"); } } else if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { prev = interpretGetString(program, opcode[1], data[1]); } else { interpretError("Error, invalid arg 2 to tokenize. (string)"); } opcode[2] = interpretPopShort(program); data[2] = interpretPopLong(program); if (opcode[2] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[2], data[2]); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Error, invalid arg 1 to tokenize."); } char* string = interpretGetString(program, opcode[2], data[2]); char* temp = NULL; if (prev != NULL) { char* start = strstr(string, prev); if (start != NULL) { start += strlen(prev); while (*start != data[0] && *start != '\0') { start++; } } if (*start == data[0]) { int length = 0; char* end = start + 1; while (*end != data[0] && *end != '\0') { end++; length++; } temp = (char*)mycalloc(1, length + 1, __FILE__, __LINE__); // "..\\int\\INTLIB.C, 230 strncpy(temp, start, length); interpretPushLong(program, interpretAddString(program, temp)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } else { interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } } else { int length = 0; char* end = string; while (*end != data[0] && *end != '\0') { end++; length++; } if (string != NULL) { temp = (char*)mycalloc(1, length + 1, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 248 strncpy(temp, string, length); interpretPushLong(program, interpretAddString(program, temp)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } else { interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } } if (temp != NULL) { myfree(temp, __FILE__, __LINE__); // "..\\int\\INTLIB.C" , 260 } } // 0x461F1C static void op_printrect(Program* program) { selectWindowID(program->windowId); opcode_t opcode[3]; int data[3]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT || data[0] > 2) { interpretError("Invalid arg 3 given to printrect, expecting int"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to printrect, expecting int"); } char string[80]; switch (opcode[2] & VALUE_TYPE_MASK) { case VALUE_TYPE_STRING: sprintf(string, "%s", interpretGetString(program, opcode[2], data[2])); break; case VALUE_TYPE_FLOAT: sprintf(string, "%.5f", *((float*)&data[2])); break; case VALUE_TYPE_INT: sprintf(string, "%d", data[2]); break; } if (!windowPrintRect(string, data[1], data[0])) { interpretError("Error in printrect"); } } // 0x46209C static void op_selectwin(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to select"); } const char* windowName = interpretGetString(program, opcode, data); int win = pushWindow(windowName); if (win == -1) { interpretError("Error selecing window %s\n", interpretGetString(program, opcode, data)); } program->windowId = win; interpretOutputFunc(windowOutput); } // 0x46213C static void op_display(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to display"); } char* fileName = interpretGetString(program, opcode, data); selectWindowID(program->windowId); char* mangledFileName = interpretMangleName(fileName); displayFile(mangledFileName); } // 0x4621B4 static void op_displayraw(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to displayraw"); } char* fileName = interpretGetString(program, opcode, data); selectWindowID(program->windowId); char* mangledFileName = interpretMangleName(fileName); displayFileRaw(mangledFileName); } // 0x46222C static void interpretFadePaletteBK(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration, int shouldProcessBk) { unsigned int time; unsigned int previousTime; unsigned int delta; int step; int steps; int index; unsigned char palette[256 * 3]; time = get_time(); previousTime = time; steps = (int)duration; step = 0; delta = 0; if (duration != 0.0) { while (step < steps) { if (delta != 0) { for (index = 0; index < 768; index++) { palette[index] = oldPalette[index] - (oldPalette[index] - newPalette[index]) * step / steps; } setSystemPalette(palette); previousTime = time; step += delta; } if (shouldProcessBk) { process_bk(); } time = get_time(); delta = time - previousTime; } } setSystemPalette(newPalette); } // NOTE: Unused. // // 0x462330 void interpretFadePalette(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration) { interpretFadePaletteBK(oldPalette, newPalette, a3, duration, 1); } // NOTE: Unused. int intlibGetFadeIn() { return currentlyFadedIn; } // NOTE: Inlined. // // 0x462348 void interpretFadeOut(float duration) { int cursorWasHidden; cursorWasHidden = mouse_hidden(); mouse_hide(); interpretFadePaletteBK(getSystemPalette(), blackPal, 64, duration, 1); if (!cursorWasHidden) { mouse_show(); } } // NOTE: Inlined. // // 0x462380 void interpretFadeIn(float duration) { interpretFadePaletteBK(blackPal, cmap, 64, duration, 1); } // NOTE: Unused. // // 0x4623A4 void interpretFadeOutNoBK(float duration) { int cursorWasHidden; cursorWasHidden = mouse_hidden(); mouse_hide(); interpretFadePaletteBK(getSystemPalette(), blackPal, 64, duration, 0); if (!cursorWasHidden) { mouse_show(); } } // NOTE: Unused. // // 0x4623DC void interpretFadeInNoBK(float duration) { interpretFadePaletteBK(blackPal, cmap, 64, duration, 0); } // 0x462400 static void op_fadein(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid type given to fadein\n"); } program->flags |= PROGRAM_FLAG_0x20; setSystemPalette(blackPal); // NOTE: Uninline. interpretFadeIn((float)data); currentlyFadedIn = true; program->flags &= ~PROGRAM_FLAG_0x20; } // 0x4624B4 static void op_fadeout(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { // FIXME: Wrong function name, should be fadeout. interpretError("Invalid type given to fadein\n"); } program->flags |= PROGRAM_FLAG_0x20; // NOTE: Uninline. interpretFadeOut((float)data); currentlyFadedIn = false; program->flags &= ~PROGRAM_FLAG_0x20; } // 0x462570 int checkMovie(Program* program) { if (dialogGetDialogDepth() > 0) { return 1; } return windowMoviePlaying(); } // 0x462584 static void op_movieflags(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (!windowSetMovieFlags(data)) { interpretError("Error setting movie flags\n"); } } // 0x4625D0 static void op_playmovie(Program* program) { // 0x59E168 static char name[100]; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to playmovie"); } strcpy(name, interpretGetString(program, opcode, data)); if (strrchr(name, '.') == NULL) { strcat(name, ".mve"); } selectWindowID(program->windowId); program->flags |= PROGRAM_IS_WAITING; program->checkWaitFunc = checkMovie; char* mangledFileName = interpretMangleName(name); if (!windowPlayMovie(mangledFileName)) { interpretError("Error playing movie"); } } // 0x4626C4 static void op_playmovierect(Program* program) { // 0x59E1CC static char name[100]; opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { // FIXME: Wrong function name, should playmovierect. interpretError("Invalid arg given to playmovie"); } strcpy(name, interpretGetString(program, opcode[4], data[4])); if (strrchr(name, '.') == NULL) { strcat(name, ".mve"); } selectWindowID(program->windowId); program->checkWaitFunc = checkMovie; program->flags |= PROGRAM_IS_WAITING; char* mangledFileName = interpretMangleName(name); if (!windowPlayMovieRect(mangledFileName, data[3], data[2], data[1], data[0])) { interpretError("Error playing movie"); } } // 0x46287C static void op_stopmovie(Program* program) { windowStopMovie(); program->flags |= PROGRAM_FLAG_0x40; } // 0x462890 static void op_deleteregion(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data != -1) { interpretError("Invalid type given to deleteregion"); } } selectWindowID(program->windowId); const char* regionName = data != -1 ? interpretGetString(program, opcode, data) : NULL; windowDeleteRegion(regionName); } // 0x462924 static void op_activateregion(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* regionName = interpretGetString(program, opcode[1], data[1]); windowActivateRegion(regionName, data[0]); } // 0x4629A0 static void op_checkregion(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid arg 1 given to checkregion();\n"); } const char* regionName = interpretGetString(program, opcode, data); bool regionExists = windowCheckRegionExists(regionName); interpretPushLong(program, regionExists); interpretPushShort(program, VALUE_TYPE_INT); } // 0x462A1C static void op_addregion(Program* program) { opcode_t opcode; int data; opcode = interpretPopShort(program); data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid number of elements given to region"); } int args = data; if (args < 2) { interpretError("addregion call without enough points!"); } selectWindowID(program->windowId); windowStartRegion(args / 2); while (args >= 2) { opcode = interpretPopShort(program); data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid y value given to region"); } int y = data; opcode = interpretPopShort(program); data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid x value given to region"); } int x = data; y = (y * windowGetYres() + 479) / 480; x = (x * windowGetXres() + 639) / 640; args -= 2; windowAddRegionPoint(x, y, true); } if (args == 0) { interpretError("Unnamed regions not allowed\n"); windowEndRegion(); } else { opcode = interpretPopShort(program); data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && opcode == VALUE_TYPE_INT) { if (data != 0) { interpretError("Invalid name given to region"); } } const char* regionName = interpretGetString(program, opcode, data); windowAddRegionName(regionName); windowEndRegion(); } } // 0x462C10 static void op_addregionproc(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 4 name given to addregionproc"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 3 name given to addregionproc"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 2 name given to addregionproc"); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 1 name given to addregionproc"); } if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid name given to addregionproc"); } const char* regionName = interpretGetString(program, opcode[4], data[4]); selectWindowID(program->windowId); if (!windowAddRegionProc(regionName, program, data[3], data[2], data[1], data[0])) { interpretError("Error setting procedures to region %s\n", regionName); } } // 0x462DDC static void op_addregionrightproc(Program* program) { opcode_t opcode[3]; int data[3]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 2 name given to addregionrightproc"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 1 name given to addregionrightproc"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid name given to addregionrightproc"); } const char* regionName = interpretGetString(program, opcode[2], data[2]); selectWindowID(program->windowId); if (!windowAddRegionRightProc(regionName, program, data[1], data[0])) { interpretError("ErrorError setting right button procedures to region %s\n", regionName); } } // 0x462F08 static void op_createwin(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* windowName = interpretGetString(program, opcode[4], data[4]); int x = (data[3] * windowGetXres() + 639) / 640; int y = (data[2] * windowGetYres() + 479) / 480; int width = (data[1] * windowGetXres() + 639) / 640; int height = (data[0] * windowGetYres() + 479) / 480; if (createWindow(windowName, x, y, width, height, colorTable[0], 0) == -1) { interpretError("Couldn't create window."); } } // 0x46308C static void op_resizewin(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* windowName = interpretGetString(program, opcode[4], data[4]); int x = (data[3] * windowGetXres() + 639) / 640; int y = (data[2] * windowGetYres() + 479) / 480; int width = (data[1] * windowGetXres() + 639) / 640; int height = (data[0] * windowGetYres() + 479) / 480; if (resizeWindow(windowName, x, y, width, height) == -1) { interpretError("Couldn't resize window."); } } // 0x463204 static void op_scalewin(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* windowName = interpretGetString(program, opcode[4], data[4]); int x = (data[3] * windowGetXres() + 639) / 640; int y = (data[2] * windowGetYres() + 479) / 480; int width = (data[1] * windowGetXres() + 639) / 640; int height = (data[0] * windowGetYres() + 479) / 480; if (scaleWindow(windowName, x, y, width, height) == -1) { interpretError("Couldn't scale window."); } } // 0x46337C static void op_deletewin(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } const char* windowName = interpretGetString(program, opcode, data); if (!deleteWindow(windowName)) { interpretError("Error deleting window %s\n", windowName); } program->windowId = popWindow(); } // 0x4633E4 static void op_saystart(Program* program) { sayStartingPosition = 0; program->flags |= PROGRAM_FLAG_0x20; int rc = dialogStart(program); program->flags &= ~PROGRAM_FLAG_0x20; if (rc != 0) { interpretError("Error starting dialog."); } } // 0x463430 static void op_saystartpos(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } sayStartingPosition = data; program->flags |= PROGRAM_FLAG_0x20; int rc = dialogStart(program); program->flags &= ~PROGRAM_FLAG_0x20; if (rc != 0) { interpretError("Error starting dialog."); } } // 0x46349C static void op_sayreplytitle(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } char* string = NULL; if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode, data); } if (dialogTitle(string) != 0) { interpretError("Error setting title."); } } // 0x463510 static void op_saygotoreply(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } char* string = NULL; if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode, data); } if (dialogGotoReply(string) != 0) { interpretError("Error during goto, couldn't find reply target %s", string); } } // 0x463584 static void op_sayreply(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* v1; if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[1], data[1]); } else { v1 = NULL; } if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { const char* v2 = interpretGetString(program, opcode[0], data[0]); if (dialogOption(v1, v2) != 0) { program->flags &= ~PROGRAM_FLAG_0x20; interpretError("Error setting option."); } } else if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (dialogOptionProc(v1, data[0]) != 0) { program->flags &= ~PROGRAM_FLAG_0x20; interpretError("Error setting option."); } } else { interpretError("Invalid arg 2 to sayOption"); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x4636A0 static void op_sayoption(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* v1; if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[1], data[1]); } else { v1 = NULL; } const char* v2; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v2 = interpretGetString(program, opcode[0], data[0]); } else { v2 = NULL; } if (dialogReply(v1, v2) != 0) { program->flags &= ~PROGRAM_FLAG_0x20; interpretError("Error setting option."); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x46378C static int checkDialog(Program* program) { program->flags |= PROGRAM_FLAG_0x40; return dialogGetDialogDepth() != -1; } // 0x4637A4 static void op_sayend(Program* program) { program->flags |= PROGRAM_FLAG_0x20; int rc = dialogGo(sayStartingPosition); program->flags &= ~PROGRAM_FLAG_0x20; if (rc == -2) { program->checkWaitFunc = checkDialog; program->flags |= PROGRAM_IS_WAITING; } } // 0x4637EC static void op_saygetlastpos(Program* program) { int value = dialogGetExitPoint(); interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x463810 static void op_sayquit(Program* program) { if (dialogQuit() != 0) { interpretError("Error quitting option."); } } // NOTE: Unused. // // 0x463828 int getTimeOut() { return TimeOut; } // NOTE: Unused. // // 0x463830 void setTimeOut(int value) { TimeOut = value; } // 0x463838 static void op_saymessagetimeout(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } // TODO: What the hell is this? if ((opcode & VALUE_TYPE_MASK) == 0x4000) { interpretError("sayMsgTimeout: invalid var type passed."); } TimeOut = data; } // 0x463890 static void op_saymessage(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* v1; if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[1], data[1]); } else { v1 = NULL; } const char* v2; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v2 = interpretGetString(program, opcode[0], data[0]); } else { v2 = NULL; } if (dialogMessage(v1, v2, TimeOut) != 0) { program->flags &= ~PROGRAM_FLAG_0x20; interpretError("Error setting option."); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x463980 static void op_gotoxy(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT || (opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid operand given to gotoxy"); } selectWindowID(program->windowId); int x = data[1]; int y = data[0]; windowGotoXY(x, y); } // 0x463A38 static void op_addbuttonflag(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to addbuttonflag"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid arg 1 given to addbuttonflag"); } const char* buttonName = interpretGetString(program, opcode[1], data[1]); if (!windowSetButtonFlag(buttonName, data[0])) { // NOTE: Original code calls interpretGetString one more time with the // same params. interpretError("Error setting flag on button %s", buttonName); } } // 0x463B10 static void op_addregionflag(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to addregionflag"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid arg 1 given to addregionflag"); } const char* regionName = interpretGetString(program, opcode[1], data[1]); if (!windowSetRegionFlag(regionName, data[0])) { // NOTE: Original code calls interpretGetString one more time with the // same params. interpretError("Error setting flag on region %s", regionName); } } // 0x463BE8 static void op_addbutton(Program* program) { opcode_t opcode[5]; int data[5]; opcode[0] = interpretPopShort(program); data[0] = interpretPopLong(program); if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[0], data[0]); } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid height given to addbutton"); } opcode[1] = interpretPopShort(program); data[1] = interpretPopLong(program); if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[1], data[1]); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid width given to addbutton"); } opcode[2] = interpretPopShort(program); data[2] = interpretPopLong(program); if (opcode[2] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[2], data[2]); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid y given to addbutton"); } opcode[3] = interpretPopShort(program); data[3] = interpretPopLong(program); if (opcode[3] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[3], data[3]); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid x given to addbutton"); } opcode[4] = interpretPopShort(program); data[4] = interpretPopLong(program); if (opcode[4] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[4], data[4]); } if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid name given to addbutton"); } selectWindowID(program->windowId); int height = (data[0] * windowGetYres() + 479) / 480; int width = (data[1] * windowGetXres() + 639) / 640; int y = (data[2] * windowGetYres() + 479) / 480; int x = (data[3] * windowGetXres() + 639) / 640; const char* buttonName = interpretGetString(program, opcode[4], data[4]); windowAddButton(buttonName, x, y, width, height, 0); } // 0x463DF4 static void op_addbuttontext(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid text string given to addbuttontext"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid name string given to addbuttontext"); } const char* text = interpretGetString(program, opcode[0], data[0]); const char* buttonName = interpretGetString(program, opcode[1], data[1]); if (!windowAddButtonText(buttonName, text)) { interpretError("Error setting text to button %s\n", interpretGetString(program, opcode[1], data[1])); } } // 0x463EEC static void op_addbuttongfx(Program* program) { opcode_t opcode[4]; int data[4]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if (((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING || ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[2] == 0)) || ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING || ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[1] == 0)) || ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING || ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[0] == 0))) { if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { // FIXME: Wrong function name, should be addbuttongfx. interpretError("Invalid name given to addbuttontext"); } const char* buttonName = interpretGetString(program, opcode[3], data[3]); char* pressedFileName = interpretMangleName(interpretGetString(program, opcode[2], data[2])); char* normalFileName = interpretMangleName(interpretGetString(program, opcode[1], data[1])); char* hoverFileName = interpretMangleName(interpretGetString(program, opcode[0], data[0])); selectWindowID(program->windowId); if (!windowAddButtonGfx(buttonName, pressedFileName, normalFileName, hoverFileName)) { interpretError("Error setting graphics to button %s\n", buttonName); } } else { interpretError("Invalid filename given to addbuttongfx"); } } // 0x4640DC static void op_addbuttonproc(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 4 name given to addbuttonproc"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 3 name given to addbuttonproc"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 2 name given to addbuttonproc"); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 1 name given to addbuttonproc"); } if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid name given to addbuttonproc"); } const char* buttonName = interpretGetString(program, opcode[4], data[4]); selectWindowID(program->windowId); if (!windowAddButtonProc(buttonName, program, data[3], data[2], data[1], data[0])) { interpretError("Error setting procedures to button %s\n", buttonName); } } // 0x4642A8 static void op_addbuttonrightproc(Program* program) { opcode_t opcode[3]; int data[3]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 2 name given to addbuttonrightproc"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure 1 name given to addbuttonrightproc"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid name given to addbuttonrightproc"); } const char* regionName = interpretGetString(program, opcode[2], data[2]); selectWindowID(program->windowId); if (!windowAddRegionRightProc(regionName, program, data[1], data[0])) { interpretError("Error setting right button procedures to button %s\n", regionName); } } // 0x4643D4 static void op_showwin(Program* program) { selectWindowID(program->windowId); windowDraw(); } // 0x4643E4 static void op_deletebutton(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data != -1) { interpretError("Invalid type given to delete button"); } } selectWindowID(program->windowId); if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (windowDeleteButton(NULL)) { return; } } else { const char* buttonName = interpretGetString(program, opcode, data); if (windowDeleteButton(buttonName)) { return; } } interpretError("Error deleting button"); } // 0x46449C static void op_fillwin(Program* program) { opcode_t opcode[3]; int data[3]; float* floats = (float*)data; // NOTE: Original code does not use loop. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) { if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[2] == 1) { floats[2] = 1.0; } else if (data[2] != 0) { interpretError("Invalid red value given to fillwin"); } } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) { if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[1] == 1) { floats[1] = 1.0; } else if (data[1] != 0) { interpretError("Invalid green value given to fillwin"); } } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) { if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[0] == 1) { floats[0] = 1.0; } else if (data[0] != 0) { interpretError("Invalid blue value given to fillwin"); } } } selectWindowID(program->windowId); windowFill(floats[2], floats[1], floats[0]); } // 0x4645FC static void op_fillrect(Program* program) { opcode_t opcode[7]; int data[7]; float* floats = (float*)data; // NOTE: Original code does not use loop. for (int arg = 0; arg < 7; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) { if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[2] == 1) { floats[2] = 1.0; } else if (data[2] != 0) { interpretError("Invalid red value given to fillrect"); } } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) { if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[1] == 1) { floats[1] = 1.0; } else if (data[1] != 0) { interpretError("Invalid green value given to fillrect"); } } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT) { if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[0] == 1) { floats[0] = 1.0; } else if (data[0] != 0) { interpretError("Invalid blue value given to fillrect"); } } } if ((opcode[6] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to fillrect"); } if ((opcode[5] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to fillrect"); } if ((opcode[4] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 3 given to fillrect"); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 4 given to fillrect"); } selectWindowID(program->windowId); windowFillRect(data[6], data[5], data[4], data[3], floats[2], floats[1], floats[0]); } // 0x46489C static void op_hidemouse(Program* program) { mouse_hide(); } // 0x4648A4 static void op_showmouse(Program* program) { mouse_show(); } // 0x4648AC static void op_mouseshape(Program* program) { opcode_t opcode[3]; int data[3]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 3 given to mouseshape"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to mouseshape"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid filename given to mouseshape"); } char* fileName = interpretGetString(program, opcode[2], data[2]); if (!mouseSetMouseShape(fileName, data[1], data[0])) { interpretError("Error loading mouse shape."); } } // 0x4649C4 static void op_setglobalmousefunc(Program* Program) { interpretError("setglobalmousefunc not defined"); } // 0x4649D4 static void op_displaygfx(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } char* fileName = interpretGetString(program, opcode[4], data[4]); char* mangledFileName = interpretMangleName(fileName); windowDisplay(mangledFileName, data[3], data[2], data[1], data[0]); } // 0x464ADC static void op_loadpalettetable(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { if ((opcode & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data != -1) { interpretError("Invalid type given to loadpalettetable"); } } char* path = interpretGetString(program, opcode, data); if (!loadColorTable(path)) { interpretError(colorError()); } } // 0x464B54 static void op_addNamedEvent(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to addnamedevent"); } const char* v1 = interpretGetString(program, opcode[1], data[1]); nevs_addevent(v1, program, data[0], NEVS_TYPE_EVENT); } // 0x464BE8 static void op_addNamedHandler(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to addnamedhandler"); } const char* v1 = interpretGetString(program, opcode[1], data[1]); nevs_addevent(v1, program, data[0], NEVS_TYPE_HANDLER); } // 0x464C80 static void op_clearNamed(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to clearnamed"); } char* string = interpretGetString(program, opcode, data); nevs_clearevent(string); } // 0x464CE4 static void op_signalNamed(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to signalnamed"); } char* str = interpretGetString(program, opcode, data); nevs_signal(str); } // 0x464D48 static void op_addkey(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 2; arg++) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg %d given to addkey", arg + 1); } } int key = data[1]; int proc = data[0]; if (key == -1) { anyKeyOffset = proc; anyKeyProg = program; } else { if (key > INT_LIB_KEY_HANDLERS_CAPACITY - 1) { interpretError("Key out of range"); } inputProc[key].program = program; inputProc[key].proc = proc; } } // 0x464E24 static void op_deletekey(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to deletekey"); } int key = data; if (key == -1) { anyKeyOffset = 0; anyKeyProg = NULL; } else { if (key > INT_LIB_KEY_HANDLERS_CAPACITY - 1) { interpretError("Key out of range"); } inputProc[key].program = NULL; inputProc[key].proc = 0; } } // 0x464EB0 static void op_refreshmouse(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to refreshmouse"); } if (!windowRefreshRegions()) { executeProc(program, data); } } // 0x464F18 static void op_setfont(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to setfont"); } if (!windowSetFont(data)) { interpretError("Error setting font"); } } // 0x464F84 static void op_settextflags(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to setflags"); } if (!windowSetTextFlags(data)) { interpretError("Error setting text flags"); } } // 0x464FF0 static void op_settextcolor(Program* program) { opcode_t opcode[3]; int data[3]; float* floats = (float*)data; // NOTE: Original code does not use loops. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 3; arg++) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[arg] != 0) { interpretError("Invalid type given to settextcolor"); } } float r = floats[2]; float g = floats[1]; float b = floats[0]; if (!windowSetTextColor(r, g, b)) { interpretError("Error setting text color"); } } // 0x465140 static void op_sayoptioncolor(Program* program) { opcode_t opcode[3]; int data[3]; float* floats = (float*)data; // NOTE: Original code does not use loops. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 3; arg++) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[arg] != 0) { interpretError("Invalid type given to sayoptioncolor"); } } float r = floats[2]; float g = floats[1]; float b = floats[0]; if (dialogSetOptionColor(r, g, b)) { interpretError("Error setting option color"); } } // 0x465290 static void op_sayreplycolor(Program* program) { opcode_t opcode[3]; int data[3]; float* floats = (float*)data; // NOTE: Original code does not use loops. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); ; if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 3; arg++) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[arg] != 0) { interpretError("Invalid type given to sayreplycolor"); } } float r = floats[2]; float g = floats[1]; float b = floats[0]; if (dialogSetReplyColor(r, g, b) != 0) { interpretError("Error setting reply color"); } } // 0x4653E0 static void op_sethighlightcolor(Program* program) { opcode_t opcode[3]; int data[3]; float* floats = (float*)data; // NOTE: Original code does not use loops. for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 3; arg++) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_FLOAT && (opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[arg] != 0) { interpretError("Invalid type given to sethighlightcolor"); } } float r = floats[2]; float g = floats[1]; float b = floats[0]; if (!windowSetHighlightColor(r, g, b)) { interpretError("Error setting text highlight color"); } } // 0x465530 static void op_sayreplywindow(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } char* v1; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[0], data[0]); v1 = interpretMangleName(v1); v1 = mystrdup(v1, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1510 } else if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[0] == 0) { v1 = NULL; } else { interpretError("Invalid arg 5 given to sayreplywindow"); } if (dialogSetReplyWindow(data[4], data[3], data[2], data[1], v1) != 0) { interpretError("Error setting reply window"); } } // 0x465688 static void op_sayreplyflags(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to sayreplyflags"); } if (!dialogSetReplyFlags(data)) { interpretError("Error setting reply flags"); } } // 0x4656F4 static void op_sayoptionflags(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to sayoptionflags"); } if (!dialogSetOptionFlags(data)) { interpretError("Error setting option flags"); } } // 0x465760 static void op_sayoptionwindow(Program* program) { opcode_t opcode[5]; int data[5]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } char* v1; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[0], data[0]); v1 = interpretMangleName(v1); v1 = mystrdup(v1, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1556 } else if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[0] == 0) { v1 = NULL; } else { interpretError("Invalid arg 5 given to sayoptionwindow"); } if (dialogSetOptionWindow(data[4], data[3], data[2], data[1], v1) != 0) { interpretError("Error setting option window"); } } // 0x4658B8 static void op_sayborder(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loops. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 2; arg++) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg %d given to sayborder", arg + 1); } } if (dialogSetBorder(data[1], data[0]) != 0) { interpretError("Error setting dialog border"); } } // 0x465978 static void op_sayscrollup(Program* program) { opcode_t opcode[6]; int data[6]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 6; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } char* v1 = NULL; char* v2 = NULL; char* v3 = NULL; char* v4 = NULL; int v5 = 0; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[0] != -1 && data[0] != 0) { interpretError("Invalid arg 4 given to sayscrollup"); } if (data[0] == -1) { v5 = 1; } } else { if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid arg 4 given to sayscrollup"); } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[1] != 0) { interpretError("Invalid arg 3 given to sayscrollup"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[2] != 0) { interpretError("Invalid arg 2 given to sayscrollup"); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[3] != 0) { interpretError("Invalid arg 1 given to sayscrollup"); } if ((opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[3], data[3]); v1 = interpretMangleName(v1); v1 = mystrdup(v1, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1611 } if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v2 = interpretGetString(program, opcode[2], data[2]); v2 = interpretMangleName(v2); v2 = mystrdup(v2, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1613 } if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v3 = interpretGetString(program, opcode[1], data[1]); v3 = interpretMangleName(v3); v3 = mystrdup(v3, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1615 } if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v4 = interpretGetString(program, opcode[0], data[0]); v4 = interpretMangleName(v4); v4 = mystrdup(v4, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1617 } if (dialogSetScrollUp(data[5], data[4], v1, v2, v3, v4, v5) != 0) { interpretError("Error setting scroll up"); } } // 0x465CAC static void op_sayscrolldown(Program* program) { opcode_t opcode[6]; int data[6]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 6; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } char* v1 = NULL; char* v2 = NULL; char* v3 = NULL; char* v4 = NULL; int v5 = 0; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_INT) { if (data[0] != -1 && data[0] != 0) { // FIXME: Wrong function name, should be sayscrolldown. interpretError("Invalid arg 4 given to sayscrollup"); } if (data[0] == -1) { v5 = 1; } } else { if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { // FIXME: Wrong function name, should be sayscrolldown. interpretError("Invalid arg 4 given to sayscrollup"); } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[1] != 0) { interpretError("Invalid arg 3 given to sayscrolldown"); } if ((opcode[2] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[2] != 0) { interpretError("Invalid arg 2 given to sayscrolldown"); } if ((opcode[3] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING && (opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_INT && data[3] != 0) { interpretError("Invalid arg 1 given to sayscrolldown"); } if ((opcode[3] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v1 = interpretGetString(program, opcode[3], data[3]); v1 = interpretMangleName(v1); v1 = mystrdup(v1, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1652 } if ((opcode[2] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v2 = interpretGetString(program, opcode[2], data[2]); v2 = interpretMangleName(v2); v2 = mystrdup(v2, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1654 } if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v3 = interpretGetString(program, opcode[1], data[1]); v3 = interpretMangleName(v3); v3 = mystrdup(v3, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1656 } if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { v4 = interpretGetString(program, opcode[0], data[0]); v4 = interpretMangleName(v4); v4 = mystrdup(v4, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1658 } if (dialogSetScrollDown(data[5], data[4], v1, v2, v3, v4, v5) != 0) { interpretError("Error setting scroll down"); } } // 0x465FE0 static void op_saysetspacing(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to saysetspacing"); } if (dialogSetSpacing(data) != 0) { interpretError("Error setting option spacing"); } } // 0x46604C static void op_sayrestart(Program* program) { if (dialogRestart() != 0) { interpretError("Error restarting option"); } } // 0x466064 static void soundCallbackInterpret(void* userData, int a2) { if (a2 == 1) { Sound** sound = (Sound**)userData; *sound = NULL; } } // 0x466070 static int soundDeleteInterpret(int value) { if (value == -1) { return 1; } if ((value & 0xA0000000) == 0) { return 0; } int index = value & ~0xA0000000; Sound* sound = interpretSounds[index]; if (sound == NULL) { return 0; } if (soundPlaying(sound)) { soundStop(sound); } soundDelete(sound); interpretSounds[index] = NULL; return 1; } // NOTE: Inlined. // // 0x4660E8 void soundCloseInterpret() { int index; for (index = 0; index < INT_LIB_SOUNDS_CAPACITY; index++) { if (interpretSounds[index] != NULL) { soundDeleteInterpret(index | 0xA0000000); } } } // 0x466110 int soundStartInterpret(char* fileName, int mode) { int v3 = 1; int v5 = 0; if (mode & 0x01) { // looping v5 |= 0x20; } else { v3 = 5; } if (mode & 0x02) { v5 |= 0x08; } else { v5 |= 0x10; } if (mode & 0x0100) { // memory v3 &= ~0x03; v3 |= 0x01; } if (mode & 0x0200) { // streamed v3 &= ~0x03; v3 |= 0x02; } int index; for (index = 0; index < INT_LIB_SOUNDS_CAPACITY; index++) { if (interpretSounds[index] == NULL) { break; } } if (index == INT_LIB_SOUNDS_CAPACITY) { return -1; } Sound* sound = interpretSounds[index] = soundAllocate(v3, v5); if (sound == NULL) { return -1; } soundSetCallback(sound, soundCallbackInterpret, &(interpretSounds[index])); if (mode & 0x01) { soundLoop(sound, 0xFFFF); } if (mode & 0x1000) { // mono soundSetChannel(sound, 2); } if (mode & 0x2000) { // stereo soundSetChannel(sound, 3); } int rc = soundLoad(sound, fileName); if (rc != SOUND_NO_ERROR) { goto err; } rc = soundPlay(sound); // TODO: Maybe wrong. switch (rc) { case SOUND_NO_DEVICE: debug_printf("soundPlay error: %s\n", "SOUND_NO_DEVICE"); goto err; case SOUND_NOT_INITIALIZED: debug_printf("soundPlay error: %s\n", "SOUND_NOT_INITIALIZED"); goto err; case SOUND_NO_SOUND: debug_printf("soundPlay error: %s\n", "SOUND_NO_SOUND"); goto err; case SOUND_FUNCTION_NOT_SUPPORTED: debug_printf("soundPlay error: %s\n", "SOUND_FUNC_NOT_SUPPORTED"); goto err; case SOUND_NO_BUFFERS_AVAILABLE: debug_printf("soundPlay error: %s\n", "SOUND_NO_BUFFERS_AVAILABLE"); goto err; case SOUND_FILE_NOT_FOUND: debug_printf("soundPlay error: %s\n", "SOUND_FILE_NOT_FOUND"); goto err; case SOUND_ALREADY_PLAYING: debug_printf("soundPlay error: %s\n", "SOUND_ALREADY_PLAYING"); goto err; case SOUND_NOT_PLAYING: debug_printf("soundPlay error: %s\n", "SOUND_NOT_PLAYING"); goto err; case SOUND_ALREADY_PAUSED: debug_printf("soundPlay error: %s\n", "SOUND_ALREADY_PAUSED"); goto err; case SOUND_NOT_PAUSED: debug_printf("soundPlay error: %s\n", "SOUND_NOT_PAUSED"); goto err; case SOUND_INVALID_HANDLE: debug_printf("soundPlay error: %s\n", "SOUND_INVALID_HANDLE"); goto err; case SOUND_NO_MEMORY_AVAILABLE: debug_printf("soundPlay error: %s\n", "SOUND_NO_MEMORY"); goto err; case SOUND_UNKNOWN_ERROR: debug_printf("soundPlay error: %s\n", "SOUND_ERROR"); goto err; } return index | 0xA0000000; err: soundDelete(sound); interpretSounds[index] = NULL; return -1; } // 0x46655C static int soundPauseInterpret(int value) { if (value == -1) { return 1; } if ((value & 0xA0000000) == 0) { return 0; } int index = value & ~0xA0000000; Sound* sound = interpretSounds[index]; if (sound == NULL) { return 0; } int rc; if (soundType(sound, 0x01)) { rc = soundStop(sound); } else { rc = soundPause(sound); } return rc == SOUND_NO_ERROR; } // 0x4665C8 static int soundRewindInterpret(int value) { if (value == -1) { return 1; } if ((value & 0xA0000000) == 0) { return 0; } int index = value & ~0xA0000000; Sound* sound = interpretSounds[index]; if (sound == NULL) { return 0; } if (!soundPlaying(sound)) { return 1; } soundStop(sound); return soundPlay(sound) == SOUND_NO_ERROR; } // 0x46662C static int soundUnpauseInterpret(int value) { if (value == -1) { return 1; } if ((value & 0xA0000000) == 0) { return 0; } int index = value & ~0xA0000000; Sound* sound = interpretSounds[index]; if (sound == NULL) { return 0; } int rc; if (soundType(sound, 0x01)) { rc = soundPlay(sound); } else { rc = soundUnpause(sound); } return rc == SOUND_NO_ERROR; } // 0x466698 static void op_soundplay(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 2 given to soundplay"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid arg 1 given to soundplay"); } char* fileName = interpretGetString(program, opcode[1], data[1]); char* mangledFileName = interpretMangleName(fileName); int rc = soundStartInterpret(mangledFileName, data[0]); interpretPushLong(program, rc); interpretPushShort(program, VALUE_TYPE_INT); } // 0x466768 static void op_soundpause(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (data == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to soundpause"); } soundPauseInterpret(data); } // 0x4667C0 static void op_soundresume(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (data == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to soundresume"); } soundUnpauseInterpret(data); } // 0x466818 static void op_soundstop(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (data == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to soundstop"); } soundPauseInterpret(data); } // 0x466870 static void op_soundrewind(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (data == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to soundrewind"); } soundRewindInterpret(data); } // 0x4668C8 static void op_sounddelete(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (data == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 1 given to sounddelete"); } soundDeleteInterpret(data); } // 0x466920 static void op_setoneoptpause(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("SetOneOptPause: invalid arg passed (non-integer)."); } if (data) { if ((dialogGetMediaFlag() & 8) == 0) { return; } } else { if ((dialogGetMediaFlag() & 8) != 0) { return; } } dialogToggleMediaFlag(8); } // 0x466994 void updateIntLib() { nevs_update(); updateIntExtra(); } // 0x4669A0 void intlibClose() { dialogClose(); intExtraClose(); // NOTE: Uninline. soundCloseInterpret(); nevs_close(); if (callbacks != NULL) { myfree(callbacks, __FILE__, __LINE__); // "..\\int\\INTLIB.C", 1976 callbacks = NULL; numCallbacks = 0; } } // 0x466A04 static bool intLibDoInput(int key) { if (key < 0 || key >= INT_LIB_KEY_HANDLERS_CAPACITY) { return false; } if (anyKeyProg != NULL) { if (anyKeyOffset != 0) { executeProc(anyKeyProg, anyKeyOffset); } return true; } IntLibKeyHandlerEntry* entry = &(inputProc[key]); if (entry->program == NULL) { return false; } if (entry->proc != 0) { executeProc(entry->program, entry->proc); } return true; } // 0x466A70 void initIntlib() { windowAddInputFunc(intLibDoInput); interpretAddFunc(0x806A, op_fillwin3x3); interpretAddFunc(0x808C, op_deletebutton); interpretAddFunc(0x8086, op_addbutton); interpretAddFunc(0x8088, op_addbuttonflag); interpretAddFunc(0x8087, op_addbuttontext); interpretAddFunc(0x8089, op_addbuttongfx); interpretAddFunc(0x808A, op_addbuttonproc); interpretAddFunc(0x808B, op_addbuttonrightproc); interpretAddFunc(0x8067, op_showwin); interpretAddFunc(0x8068, op_fillwin); interpretAddFunc(0x8069, op_fillrect); interpretAddFunc(0x8072, op_print); interpretAddFunc(0x8073, op_format); interpretAddFunc(0x8074, op_printrect); interpretAddFunc(0x8075, op_setfont); interpretAddFunc(0x8076, op_settextflags); interpretAddFunc(0x8077, op_settextcolor); interpretAddFunc(0x8078, op_sethighlightcolor); interpretAddFunc(0x8064, op_selectwin); interpretAddFunc(0x806B, op_display); interpretAddFunc(0x806D, op_displayraw); interpretAddFunc(0x806C, op_displaygfx); interpretAddFunc(0x806F, op_fadein); interpretAddFunc(0x8070, op_fadeout); interpretAddFunc(0x807A, op_playmovie); interpretAddFunc(0x807B, op_movieflags); interpretAddFunc(0x807C, op_playmovierect); interpretAddFunc(0x8079, op_stopmovie); interpretAddFunc(0x807F, op_addregion); interpretAddFunc(0x8080, op_addregionflag); interpretAddFunc(0x8081, op_addregionproc); interpretAddFunc(0x8082, op_addregionrightproc); interpretAddFunc(0x8083, op_deleteregion); interpretAddFunc(0x8084, op_activateregion); interpretAddFunc(0x8085, op_checkregion); interpretAddFunc(0x8062, op_createwin); interpretAddFunc(0x8063, op_deletewin); interpretAddFunc(0x8065, op_resizewin); interpretAddFunc(0x8066, op_scalewin); interpretAddFunc(0x804E, op_saystart); interpretAddFunc(0x804F, op_saystartpos); interpretAddFunc(0x8050, op_sayreplytitle); interpretAddFunc(0x8051, op_saygotoreply); interpretAddFunc(0x8053, op_sayreply); interpretAddFunc(0x8052, op_sayoption); interpretAddFunc(0x804D, op_sayend); interpretAddFunc(0x804C, op_sayquit); interpretAddFunc(0x8054, op_saymessage); interpretAddFunc(0x8055, op_sayreplywindow); interpretAddFunc(0x8056, op_sayoptionwindow); interpretAddFunc(0x805F, op_sayreplyflags); interpretAddFunc(0x8060, op_sayoptionflags); interpretAddFunc(0x8057, op_sayborder); interpretAddFunc(0x8058, op_sayscrollup); interpretAddFunc(0x8059, op_sayscrolldown); interpretAddFunc(0x805A, op_saysetspacing); interpretAddFunc(0x805B, op_sayoptioncolor); interpretAddFunc(0x805C, op_sayreplycolor); interpretAddFunc(0x805D, op_sayrestart); interpretAddFunc(0x805E, op_saygetlastpos); interpretAddFunc(0x8061, op_saymessagetimeout); interpretAddFunc(0x8071, op_gotoxy); interpretAddFunc(0x808D, op_hidemouse); interpretAddFunc(0x808E, op_showmouse); interpretAddFunc(0x8090, op_refreshmouse); interpretAddFunc(0x808F, op_mouseshape); interpretAddFunc(0x8091, op_setglobalmousefunc); interpretAddFunc(0x806E, op_loadpalettetable); interpretAddFunc(0x8092, op_addNamedEvent); interpretAddFunc(0x8093, op_addNamedHandler); interpretAddFunc(0x8094, op_clearNamed); interpretAddFunc(0x8095, op_signalNamed); interpretAddFunc(0x8096, op_addkey); interpretAddFunc(0x8097, op_deletekey); interpretAddFunc(0x8098, op_soundplay); interpretAddFunc(0x8099, op_soundpause); interpretAddFunc(0x809A, op_soundresume); interpretAddFunc(0x809B, op_soundstop); interpretAddFunc(0x809C, op_soundrewind); interpretAddFunc(0x809D, op_sounddelete); interpretAddFunc(0x809E, op_setoneoptpause); interpretAddFunc(0x809F, op_selectfilelist); interpretAddFunc(0x80A0, op_tokenize); nevs_initonce(); initIntExtra(); initDialog(); } // 0x466F6C void interpretRegisterProgramDeleteCallback(IntLibProgramDeleteCallback* callback) { int index; for (index = 0; index < numCallbacks; index++) { if (callbacks[index] == NULL) { break; } } if (index == numCallbacks) { if (callbacks != NULL) { callbacks = (IntLibProgramDeleteCallback**)myrealloc(callbacks, sizeof(*callbacks) * (numCallbacks + 1), __FILE__, __LINE__); // ..\\int\\INTLIB.C, 2110 } else { callbacks = (IntLibProgramDeleteCallback**)mymalloc(sizeof(*callbacks), __FILE__, __LINE__); // ..\\int\\INTLIB.C, 2112 } numCallbacks++; } callbacks[index] = callback; } // 0x467040 void removeProgramReferences(Program* program) { for (int index = 0; index < INT_LIB_KEY_HANDLERS_CAPACITY; index++) { if (program == inputProc[index].program) { inputProc[index].program = NULL; } } intExtraRemoveProgramReferences(program); for (int index = 0; index < numCallbacks; index++) { IntLibProgramDeleteCallback* callback = callbacks[index]; if (callback != NULL) { callback(program); } } } ================================================ FILE: src/int/intlib.h ================================================ #ifndef FALLOUT_INT_INTLIB_H_ #define FALLOUT_INT_INTLIB_H_ #include #include "int/intrpret.h" typedef void(IntLibProgramDeleteCallback)(Program*); void interpretFadePalette(unsigned char* oldPalette, unsigned char* newPalette, int a3, float duration); int intlibGetFadeIn(); void interpretFadeOut(float duration); void interpretFadeIn(float duration); void interpretFadeOutNoBK(float duration); void interpretFadeInNoBK(float duration); int checkMovie(Program* program); int getTimeOut(); void setTimeOut(int value); void soundCloseInterpret(); int soundStartInterpret(char* fileName, int mode); void updateIntLib(); void intlibClose(); void initIntlib(); void interpretRegisterProgramDeleteCallback(IntLibProgramDeleteCallback* callback); void removeProgramReferences(Program* program); #endif /* FALLOUT_INT_INTLIB_H_ */ ================================================ FILE: src/int/intrpret.c ================================================ #include "int/intrpret.h" #include #include #include #include #include #include #include "plib/gnw/input.h" #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "int/export.h" #include "int/intlib.h" #include "int/memdbg.h" // The maximum number of opcodes. #define OPCODE_MAX_COUNT 342 typedef struct ProgramListNode { Program* program; struct ProgramListNode* next; // next struct ProgramListNode* prev; // prev } ProgramListNode; static unsigned int defaultTimerFunc(); static char* defaultFilename(char* fileName); static int outputStr(char* string); static int checkWait(Program* program); static const char* findCurrentProc(Program* program); static opcode_t fetchWord(unsigned char* data, int pos); static int fetchLong(unsigned char* a1, int a2); static void storeWord(int value, unsigned char* a2, int a3); static void storeLong(int value, unsigned char* stack, int pos); static void pushShortStack(unsigned char* a1, int* a2, int value); static void pushLongStack(unsigned char* a1, int* a2, int value); static int popLongStack(unsigned char* a1, int* a2); static opcode_t popShortStack(unsigned char* a1, int* a2); static void rPushShort(Program* program, int value); static void rPushLong(Program* program, int value); static opcode_t rPopShort(Program* program); static int rPopLong(Program* program); static void detachProgram(Program* program); static void purgeProgram(Program* program); static opcode_t getOp(Program* program); static void checkProgramStrings(Program* program); static void op_noop(Program* program); static void op_const(Program* program); static void op_push_base(Program* program); static void op_pop_base(Program* program); static void op_pop_to_base(Program* program); static void op_set_global(Program* program); static void op_dump(Program* program); static void op_call_at(Program* program); static void op_call_condition(Program* program); static void op_wait(Program* program); static void op_cancel(Program* program); static void op_cancelall(Program* program); static void op_if(Program* program); static void op_while(Program* program); static void op_store(Program* program); static void op_fetch(Program* program); static void op_not_equal(Program* program); static void op_equal(Program* program); static void op_less_equal(Program* program); static void op_greater_equal(Program* program); static void op_less(Program* program); static void op_greater(Program* program); static void op_add(Program* program); static void op_sub(Program* program); static void op_mul(Program* program); static void op_div(Program* program); static void op_mod(Program* program); static void op_and(Program* program); static void op_or(Program* program); static void op_not(Program* program); static void op_negate(Program* program); static void op_bwnot(Program* program); static void op_floor(Program* program); static void op_bwand(Program* program); static void op_bwor(Program* program); static void op_bwxor(Program* program); static void op_swapa(Program* program); static void op_critical_done(Program* program); static void op_critical_start(Program* program); static void op_jmp(Program* program); static void op_call(Program* program); static void op_pop_flags(Program* program); static void op_pop_return(Program* program); static void op_pop_exit(Program* program); static void op_pop_flags_return(Program* program); static void op_pop_flags_exit(Program* program); static void op_pop_flags_return_val_exit(Program* program); static void op_pop_flags_return_val_exit_extern(Program* program); static void op_pop_flags_return_extern(Program* program); static void op_pop_flags_exit_extern(Program* program); static void op_pop_flags_return_val_extern(Program* program); static void op_pop_address(Program* program); static void op_a_to_d(Program* program); static void op_d_to_a(Program* program); static void op_exit_prog(Program* program); static void op_stop_prog(Program* program); static void op_fetch_global(Program* program); static void op_store_global(Program* program); static void op_swap(Program* program); static void op_fetch_proc_address(Program* program); static void op_pop(Program* program); static void op_dup(Program* program); static void op_store_external(Program* program); static void op_fetch_external(Program* program); static void op_export_proc(Program* program); static void op_export_var(Program* program); static void op_exit(Program* program); static void op_detach(Program* program); static void op_callstart(Program* program); static void op_spawn(Program* program); static Program* op_fork_helper(Program* program); static void op_fork(Program* program); static void op_exec(Program* program); static void op_check_arg_count(Program* program); static void op_lookup_string_proc(Program* program); static void setupCallWithReturnVal(Program* program, int address, int a3); static void setupCall(Program* program, int address, int returnAddress); static void setupExternalCallWithReturnVal(Program* program1, Program* program2, int address, int a4); static void setupExternalCall(Program* program1, Program* program2, int address, int a4); static void doEvents(); static void removeProgList(ProgramListNode* programListNode); static void insertProgram(Program* program); // 0x51903C static int enabled = 1; // 0x519040 static InterpretTimerFunc* timerFunc = defaultTimerFunc; // 0x519044 static unsigned int timerTick = 1000; // 0x519048 static InterpretMangleFunc* filenameFunc = defaultFilename; // 0x51904C static InterpretOutputFunc* outputFunc = outputStr; // 0x519050 static int cpuBurstSize = 10; // 0x59E230 static OpcodeHandler* opTable[OPCODE_MAX_COUNT]; // 0x59E788 static unsigned int suspendTime; // 0x59E78C static Program* currentProgram; // 0x59E790 static ProgramListNode* head; // 0x59E794 static int suspendEvents; // 0x4670A0 static unsigned int defaultTimerFunc() { return get_time(); } // NOTE: Unused. // // 0x4670A8 void interpretSetTimeFunc(InterpretTimerFunc* timerFunc, int timerTick) { timerFunc = timerFunc; timerTick = timerTick; } // 0x4670B4 static char* defaultFilename(char* fileName) { return fileName; } // 0x4670B8 char* interpretMangleName(char* fileName) { return filenameFunc(fileName); } // 0x4670C0 static int outputStr(char* string) { return 1; } // 0x4670C8 static int checkWait(Program* program) { return 1000 * timerFunc() / timerTick <= program->waitEnd; } // 0x4670FC void interpretOutputFunc(InterpretOutputFunc* func) { outputFunc = func; } // 0x467104 int interpretOutput(const char* format, ...) { if (outputFunc == NULL) { return 0; } char string[260]; va_list args; va_start(args, format); int rc = vsprintf(string, format, args); va_end(args); debug_printf(string); return rc; } // 0x467160 static const char* findCurrentProc(Program* program) { int procedureCount = fetchLong(program->procedures, 0); unsigned char* ptr = program->procedures + 4; int procedureOffset = fetchLong(ptr, 16); int identifierOffset = fetchLong(ptr, 0); for (int index = 0; index < procedureCount; index++) { int nextProcedureOffset = fetchLong(ptr + sizeof(Procedure), 16); if (program->instructionPointer >= procedureOffset && program->instructionPointer < nextProcedureOffset) { return (const char*)(program->identifiers + identifierOffset); } ptr += sizeof(Procedure); identifierOffset = fetchLong(ptr, 0); } return ""; } // 0x4671F0 void interpretError(const char* format, ...) { char string[260]; va_list argptr; va_start(argptr, format); vsprintf(string, format, argptr); va_end(argptr); debug_printf("\nError during execution: %s\n", string); if (currentProgram == NULL) { debug_printf("No current script"); } else { debug_printf("Current script: %s, procedure %s", currentProgram->name, findCurrentProc(currentProgram)); } if (currentProgram) { longjmp(currentProgram->env, 1); } } // 0x467290 static opcode_t fetchWord(unsigned char* data, int pos) { // TODO: The return result is probably short. opcode_t value = 0; value |= data[pos++] << 8; value |= data[pos++]; return value; } // 0x4672A4 static int fetchLong(unsigned char* data, int pos) { int value = 0; value |= data[pos++] << 24; value |= data[pos++] << 16; value |= data[pos++] << 8; value |= data[pos++] & 0xFF; return value; } // 0x4672D4 static void storeWord(int value, unsigned char* stack, int pos) { stack[pos++] = (value >> 8) & 0xFF; stack[pos] = value & 0xFF; } // NOTE: Inlined. // // 0x4672E8 static void storeLong(int value, unsigned char* stack, int pos) { stack[pos++] = (value >> 24) & 0xFF; stack[pos++] = (value >> 16) & 0xFF; stack[pos++] = (value >> 8) & 0xFF; stack[pos] = value & 0xFF; } // pushShortStack // 0x467324 static void pushShortStack(unsigned char* data, int* pointer, int value) { if (*pointer + 2 >= 0x1000) { interpretError("pushShortStack: Stack overflow."); } storeWord(value, data, *pointer); *pointer += 2; } // pushLongStack // 0x46736C static void pushLongStack(unsigned char* data, int* pointer, int value) { int v1; if (*pointer + 4 >= 0x1000) { // FIXME: Should be pushLongStack. interpretError("pushShortStack: Stack overflow."); } v1 = *pointer; storeWord(value >> 16, data, v1); storeWord(value & 0xFFFF, data, v1 + 2); *pointer = v1 + 4; } // popStackLong // 0x4673C4 static int popLongStack(unsigned char* data, int* pointer) { if (*pointer < 4) { interpretError("\nStack underflow long."); } *pointer -= 4; return fetchLong(data, *pointer); } // popStackShort // 0x4673F0 static opcode_t popShortStack(unsigned char* data, int* pointer) { if (*pointer < 2) { interpretError("\nStack underflow short."); } *pointer -= 2; // NOTE: uninline return fetchWord(data, *pointer); } // NOTE: Inlined. // // 0x467424 void _interpretIncStringRef(Program* program, opcode_t opcode, int value) { if (opcode == VALUE_TYPE_DYNAMIC_STRING) { *(short*)(program->dynamicStrings + 4 + value - 2) += 1; } } // 0x467440 void interpretDecStringRef(Program* program, opcode_t opcode, int value) { char* string; short* refcountPtr; if (opcode == VALUE_TYPE_DYNAMIC_STRING) { string = (char*)(program->dynamicStrings + 4 + value); refcountPtr = (short*)(string - 2); if (*refcountPtr != 0) { *refcountPtr -= 1; } else { debug_printf("Reference count zero for %s!\n", string); } if (*refcountPtr < 0) { debug_printf("String ref went negative, this shouldn\'t ever happen\n"); } } } // 0x46748C void interpretPushShort(Program* program, int value) { int stringOffset; pushShortStack(program->stack, &(program->stackPointer), value); if (value == VALUE_TYPE_DYNAMIC_STRING) { if (program->stackPointer >= 6) { stringOffset = fetchLong(program->stack, program->stackPointer - 6); // NOTE: Uninline. _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, stringOffset); } } } // 0x4674DC void interpretPushLong(Program* program, int value) { pushLongStack(program->stack, &(program->stackPointer), value); } // 0x4674F0 opcode_t interpretPopShort(Program* program) { return popShortStack(program->stack, &(program->stackPointer)); } // 0x467500 int interpretPopLong(Program* program) { return popLongStack(program->stack, &(program->stackPointer)); } // 0x467510 static void rPushShort(Program* program, int value) { int stringOffset; pushShortStack(program->returnStack, &(program->returnStackPointer), value); if (value == VALUE_TYPE_DYNAMIC_STRING) { if (program->stackPointer >= 6) { stringOffset = fetchLong(program->returnStack, program->returnStackPointer - 6); // NOTE: Uninline. _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, stringOffset); } } } // NOTE: Inlined. // // 0x467560 static void rPushLong(Program* program, int value) { pushLongStack(program->returnStack, &(program->returnStackPointer), value); } // 0x467574 static opcode_t rPopShort(Program* program) { opcode_t type; int v5; type = popShortStack(program->returnStack, &(program->returnStackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING && program->stackPointer >= 4) { v5 = fetchLong(program->returnStack, program->returnStackPointer - 4); interpretDecStringRef(program, type, v5); } return type; } // 0x4675B8 static int rPopLong(Program* program) { return popLongStack(program->returnStack, &(program->returnStackPointer)); } // NOTE: Inlined. // // 0x4675C8 static void detachProgram(Program* program) { Program* parent = program->parent; if (parent != NULL) { parent->flags &= ~PROGRAM_FLAG_0x20; parent->flags &= ~PROGRAM_FLAG_0x0100; if (program == parent->child) { parent->child = NULL; } } } // 0x4675F4 static void purgeProgram(Program* program) { if (!program->exited) { removeProgramReferences(program); program->exited = true; } } // 0x467614 void interpretFreeProgram(Program* program) { // NOTE: Uninline. detachProgram(program); Program* curr = program->child; while (curr != NULL) { // NOTE: Uninline. purgeProgram(curr); curr->parent = NULL; Program* next = curr->child; curr->child = NULL; curr = next; } // NOTE: Uninline. purgeProgram(program); if (program->dynamicStrings != NULL) { myfree(program->dynamicStrings, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 429 } if (program->data != NULL) { myfree(program->data, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 430 } if (program->name != NULL) { myfree(program->name, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 431 } if (program->stack != NULL) { myfree(program->stack, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 432 } if (program->returnStack != NULL) { myfree(program->returnStack, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 433 } myfree(program, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 435 } // 0x467734 Program* allocateProgram(const char* path) { File* stream = db_fopen(path, "rb"); if (stream == NULL) { char err[260]; sprintf(err, "Couldn't open %s for read\n", path); interpretError(err); return NULL; } int fileSize = db_filelength(stream); unsigned char* data = (unsigned char*)mymalloc(fileSize, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 458 db_fread(data, 1, fileSize, stream); db_fclose(stream); Program* program = (Program*)mymalloc(sizeof(Program), __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 463 memset(program, 0, sizeof(Program)); program->name = (char*)mymalloc(strlen(path) + 1, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 466 strcpy(program->name, path); program->child = NULL; program->parent = NULL; program->field_78 = -1; program->stack = (unsigned char*)mycalloc(1, 4096, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 472 program->exited = false; program->basePointer = -1; program->framePointer = -1; program->returnStack = (unsigned char*)mycalloc(1, 4096, __FILE__, __LINE__); // ..\\int\\INTRPRET.C, 473 program->data = data; program->procedures = data + 42; program->identifiers = sizeof(Procedure) * fetchLong(program->procedures, 0) + program->procedures + 4; program->staticStrings = program->identifiers + fetchLong(program->identifiers, 0) + 4; return program; } // NOTE: Inlined. // // 0x4678BC static opcode_t getOp(Program* program) { int instructionPointer; instructionPointer = program->instructionPointer; program->instructionPointer = instructionPointer + 2; // NOTE: Uninline. return fetchWord(program->data, instructionPointer); } // 0x4678E0 char* interpretGetString(Program* program, opcode_t opcode, int offset) { // The order of checks is important, because dynamic string flag is // always used with static string flag. if ((opcode & RAW_VALUE_TYPE_DYNAMIC_STRING) != 0) { return (char*)(program->dynamicStrings + 4 + offset); } if ((opcode & RAW_VALUE_TYPE_STATIC_STRING) != 0) { return (char*)(program->staticStrings + 4 + offset); } return NULL; } // 0x46790C char* interpretGetName(Program* program, int offset) { return (char*)(program->identifiers + offset); } // Loops thru heap: // - mark unreferenced blocks as free. // - merge consequtive free blocks as one large block. // // This is done by negating block length: // - positive block length - check for ref count. // - negative block length - block is free, attempt to merge with next block. // // 0x4679E0 static void checkProgramStrings(Program* program) { unsigned char* ptr; short len; unsigned char* next_ptr; short next_len; short diff; if (program->dynamicStrings == NULL) { return; } ptr = program->dynamicStrings + 4; while (*(unsigned short*)ptr != 0x8000) { len = *(short*)ptr; if (len < 0) { len = -len; next_ptr = ptr + len + 4; if (*(unsigned short*)next_ptr != 0x8000) { next_len = *(short*)next_ptr; if (next_len < 0) { diff = 4 - next_len; if (diff + len < 32766) { len += diff; *(short*)ptr += next_len - 4; } else { debug_printf("merged string would be too long, size %d %d\n", diff, len); } } } } else if (*(short*)(ptr + 2) == 0) { *(short*)ptr = -len; *(short*)(ptr + 2) = 0; } ptr += len + 4; } } // 0x467A80 int interpretAddString(Program* program, char* string) { int v27; unsigned char* v20; unsigned char* v23; if (program == NULL) { return 0; } v27 = strlen(string) + 1; // Align memory if (v27 & 1) { v27++; } if (program->dynamicStrings != NULL) { // TODO: Needs testing, lots of pointer stuff. unsigned char* heap = program->dynamicStrings + 4; while (*(unsigned short*)heap != 0x8000) { short v2 = *(short*)heap; if (v2 >= 0) { if (v2 == v27) { if (strcmp(string, (char*)(heap + 4)) == 0) { return (heap + 4) - (program->dynamicStrings + 4); } } } else { v2 = -v2; if (v2 > v27) { if (v2 - v27 <= 4) { *(short*)heap = v2; } else { *(short*)(heap + v27 + 6) = 0; *(short*)(heap + v27 + 4) = -(v2 - v27 - 4); *(short*)(heap) = v27; } *(short*)(heap + 2) = 0; strcpy((char*)(heap + 4), string); *(heap + v27 + 3) = '\0'; return (heap + 4) - (program->dynamicStrings + 4); } } heap += v2 + 4; } } else { program->dynamicStrings = (unsigned char*)mymalloc(8, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 631 *(int*)(program->dynamicStrings) = 0; *(unsigned short*)(program->dynamicStrings + 4) = 0x8000; *(short*)(program->dynamicStrings + 6) = 1; } program->dynamicStrings = (unsigned char*)myrealloc(program->dynamicStrings, *(int*)(program->dynamicStrings) + 8 + 4 + v27, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 640 v20 = program->dynamicStrings + *(int*)(program->dynamicStrings) + 4; if ((*(short*)v20 & 0xFFFF) != 0x8000) { interpretError("Internal consistancy error, string table mangled"); } *(int*)(program->dynamicStrings) += v27 + 4; *(short*)(v20) = v27; *(short*)(v20 + 2) = 0; strcpy((char*)(v20 + 4), string); v23 = v20 + v27; *(v23 + 3) = '\0'; *(unsigned short*)(v23 + 4) = 0x8000; *(short*)(v23 + 6) = 1; return v20 + 4 - (program->dynamicStrings + 4); } // 0x467C90 static void op_noop(Program* program) { } // 0x467C94 static void op_const(Program* program) { int pos = program->instructionPointer; program->instructionPointer = pos + 4; int value = fetchLong(program->data, pos); pushLongStack(program->stack, &(program->stackPointer), value); interpretPushShort(program, (program->flags >> 16) & 0xFFFF); } // - Pops value from stack, which is a number of arguments in the procedure. // - Saves current frame pointer in return stack. // - Sets frame pointer to the stack pointer minus number of arguments. // // 0x467CD0 static void op_push_base(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int value = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, value); } pushLongStack(program->returnStack, &(program->returnStackPointer), program->framePointer); rPushShort(program, VALUE_TYPE_INT); program->framePointer = program->stackPointer - 6 * value; } // pop_base // 0x467D3C static void op_pop_base(Program* program) { opcode_t opcode = rPopShort(program); int data = popLongStack(program->returnStack, &(program->returnStackPointer)); if (opcode != VALUE_TYPE_INT) { char err[260]; sprintf(err, "Invalid type given to pop_base: %x", opcode); interpretError(err); } program->framePointer = data; } // 0x467D94 static void op_pop_to_base(Program* program) { while (program->stackPointer != program->framePointer) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } } } // 0x467DE0 static void op_set_global(Program* program) { program->basePointer = program->stackPointer; } // 0x467DEC static void op_dump(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (opcode != VALUE_TYPE_INT) { char err[256]; sprintf(err, "Invalid type given to dump, %x", opcode); interpretError(err); } // NOTE: Original code is slightly different - it goes backwards to -1. for (int index = 0; index < data; index++) { opcode = popShortStack(program->stack, &(program->stackPointer)); data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } } } // 0x467EA4 static void op_call_at(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = popShortStack(program->stack, &(program->stackPointer)); data[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if (arg == 0) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure type given to call"); } } else if (arg == 1) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid time given to call"); } } } unsigned char* procedure_ptr = program->procedures + 4 + sizeof(Procedure) * data[0]; int delay = 1000 * data[1]; if (!suspendEvents) { delay += 1000 * timerFunc() / timerTick; } int flags = fetchLong(procedure_ptr, 4); storeLong(delay, procedure_ptr, 8); storeLong(flags | PROCEDURE_FLAG_TIMED, procedure_ptr, 4); } // 0x468034 static void op_call_condition(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = popShortStack(program->stack, &(program->stackPointer)); data[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid procedure type given to conditional call"); } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid address given to conditional call"); } unsigned char* procedure_ptr = program->procedures + 4 + sizeof(Procedure) * data[0]; int flags = fetchLong(procedure_ptr, 4); storeLong(flags | PROCEDURE_FLAG_CONDITIONAL, procedure_ptr, 4); storeLong(data[1], procedure_ptr, 12); } // 0x46817C static void op_wait(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid type given to wait\n"); } program->waitStart = 1000 * timerFunc() / timerTick; program->waitEnd = program->waitStart + data; program->checkWaitFunc = checkWait; program->flags |= PROGRAM_IS_WAITING; } // 0x468218 static void op_cancel(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("invalid type given to cancel"); } if (data >= fetchLong(program->procedures, 0)) { interpretError("Invalid procedure offset given to cancel"); } Procedure* proc = (Procedure*)(program->procedures + 4 + data * sizeof(*proc)); proc->field_4 = 0; proc->field_8 = 0; proc->field_C = 0; } // 0x468330 static void op_cancelall(Program* program) { int procedureCount = fetchLong(program->procedures, 0); for (int index = 0; index < procedureCount; index++) { // TODO: Original code uses different approach, check. Procedure* proc = (Procedure*)(program->procedures + 4 + index * sizeof(*proc)); proc->field_4 = 0; proc->field_8 = 0; proc->field_C = 0; } } // 0x468400 static void op_if(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (data) { opcode = popShortStack(program->stack, &(program->stackPointer)); data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } } else { opcode = popShortStack(program->stack, &(program->stackPointer)); data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } program->instructionPointer = data; } } // 0x4684A4 static void op_while(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (data == 0) { opcode = popShortStack(program->stack, &(program->stackPointer)); data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } program->instructionPointer = data; } } // 0x468518 static void op_store(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = popShortStack(program->stack, &(program->stackPointer)); data[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } int var_address = program->framePointer + 6 * data[0]; // NOTE: original code is different, does not use reading functions opcode_t var_type = fetchWord(program->stack, var_address + 4); int var_value = fetchLong(program->stack, var_address); if (var_type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, var_type, var_value); } // TODO: Original code is different, check. storeLong(data[1], program->stack, var_address); storeWord(opcode[1], program->stack, var_address + 4); if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) { // NOTE: Uninline. _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, data[1]); } } // fetch // 0x468678 static void op_fetch(Program* program) { char err[256]; opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (opcode != VALUE_TYPE_INT) { sprintf(err, "Invalid type given to fetch, %x", opcode); interpretError(err); } // NOTE: original code is a bit different int variableAddress = program->framePointer + 6 * data; int variableType = fetchWord(program->stack, variableAddress + 4); int variableValue = fetchLong(program->stack, variableAddress); interpretPushLong(program, variableValue); interpretPushShort(program, variableType); } // 0x46873C static void op_not_equal(Program* program) { opcode_t opcode[2]; int data[2]; float* floats = (float*)data; char text[2][80]; char* str_ptr[2]; int res; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = popShortStack(program->stack, &(program->stackPointer)); data[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } switch (opcode[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, opcode[1], data[1]); switch (opcode[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, opcode[0], data[0]); break; case VALUE_TYPE_FLOAT: sprintf(text[0], "%.5f", floats[0]); str_ptr[0] = text[0]; break; case VALUE_TYPE_INT: sprintf(text[0], "%d", data[0]); str_ptr[0] = text[0]; break; default: assert(false && "Should be unreachable"); } res = strcmp(str_ptr[1], str_ptr[0]) != 0; break; case VALUE_TYPE_FLOAT: switch (opcode[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%.5f", floats[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, opcode[0], data[0]); res = strcmp(str_ptr[1], str_ptr[0]) != 0; break; case VALUE_TYPE_FLOAT: res = floats[1] != floats[0]; break; case VALUE_TYPE_INT: res = floats[1] != (float)data[0]; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (opcode[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%d", data[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, opcode[0], data[0]); res = strcmp(str_ptr[1], str_ptr[0]) != 0; break; case VALUE_TYPE_FLOAT: res = (float)data[1] != floats[0]; break; case VALUE_TYPE_INT: res = data[1] != data[0]; break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), res); interpretPushShort(program, VALUE_TYPE_INT); } // 0x468AA8 static void op_equal(Program* program) { int arg; opcode_t type[2]; int value[2]; float* floats = (float*)&value; char text[2][80]; char* str_ptr[2]; int res; for (arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } switch (type[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, type[1], value[1]); switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, type[0], value[0]); break; case VALUE_TYPE_FLOAT: sprintf(text[0], "%.5f", floats[0]); str_ptr[0] = text[0]; break; case VALUE_TYPE_INT: sprintf(text[0], "%d", value[0]); str_ptr[0] = text[0]; break; default: assert(false && "Should be unreachable"); } res = strcmp(str_ptr[1], str_ptr[0]) == 0; break; case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%.5f", floats[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) == 0; break; case VALUE_TYPE_FLOAT: res = floats[1] == floats[0]; break; case VALUE_TYPE_INT: res = floats[1] == (float)value[0]; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%d", value[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) == 0; break; case VALUE_TYPE_FLOAT: res = (float)value[1] == floats[0]; break; case VALUE_TYPE_INT: res = value[1] == value[0]; break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), res); interpretPushShort(program, VALUE_TYPE_INT); } // 0x468E14 static void op_less_equal(Program* program) { int arg; opcode_t type[2]; int value[2]; float* floats = (float*)&value; char text[2][80]; char* str_ptr[2]; int res; for (arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } switch (type[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, type[1], value[1]); switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, type[0], value[0]); break; case VALUE_TYPE_FLOAT: sprintf(text[0], "%.5f", floats[0]); str_ptr[0] = text[0]; break; case VALUE_TYPE_INT: sprintf(text[0], "%d", value[0]); str_ptr[0] = text[0]; break; default: assert(false && "Should be unreachable"); } res = strcmp(str_ptr[1], str_ptr[0]) <= 0; break; case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%.5f", floats[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) <= 0; break; case VALUE_TYPE_FLOAT: res = floats[1] <= floats[0]; break; case VALUE_TYPE_INT: res = floats[1] <= (float)value[0]; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%d", value[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) <= 0; break; case VALUE_TYPE_FLOAT: res = (float)value[1] <= floats[0]; break; case VALUE_TYPE_INT: res = value[1] <= value[0]; break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), res); interpretPushShort(program, VALUE_TYPE_INT); } // 0x469180 static void op_greater_equal(Program* program) { int arg; opcode_t type[2]; int value[2]; float* floats = (float*)&value; char text[2][80]; char* str_ptr[2]; int res; // NOTE: original code does not use loop for (arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } switch (type[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, type[1], value[1]); switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, type[0], value[0]); break; case VALUE_TYPE_FLOAT: sprintf(text[0], "%.5f", floats[0]); str_ptr[0] = text[0]; break; case VALUE_TYPE_INT: sprintf(text[0], "%d", value[0]); str_ptr[0] = text[0]; break; default: assert(false && "Should be unreachable"); } res = strcmp(str_ptr[1], str_ptr[0]) >= 0; break; case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%.5f", floats[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) >= 0; break; case VALUE_TYPE_FLOAT: res = floats[1] >= floats[0]; break; case VALUE_TYPE_INT: res = floats[1] >= (float)value[0]; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%d", value[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) >= 0; break; case VALUE_TYPE_FLOAT: res = (float)value[1] >= floats[0]; break; case VALUE_TYPE_INT: res = value[1] >= value[0]; break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), res); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4694EC static void op_less(Program* program) { opcode_t opcodes[2]; int values[2]; float* floats = (float*)&values; char text[2][80]; char* str_ptr[2]; int res; for (int arg = 0; arg < 2; arg++) { opcodes[arg] = popShortStack(program->stack, &(program->stackPointer)); values[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcodes[arg], values[arg]); } } switch (opcodes[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, opcodes[1], values[1]); switch (opcodes[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, opcodes[0], values[0]); break; case VALUE_TYPE_FLOAT: sprintf(text[0], "%.5f", floats[0]); str_ptr[0] = text[0]; break; case VALUE_TYPE_INT: sprintf(text[0], "%d", values[0]); str_ptr[0] = text[0]; break; default: assert(false && "Should be unreachable"); } res = strcmp(str_ptr[1], str_ptr[0]) < 0; break; case VALUE_TYPE_FLOAT: switch (opcodes[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%.5f", floats[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, opcodes[0], values[0]); res = strcmp(str_ptr[1], str_ptr[0]) < 0; break; case VALUE_TYPE_FLOAT: res = floats[1] < floats[0]; break; case VALUE_TYPE_INT: res = floats[1] < (float)values[0]; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (opcodes[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%d", values[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, opcodes[0], values[0]); res = strcmp(str_ptr[1], str_ptr[0]) < 0; break; case VALUE_TYPE_FLOAT: res = (float)values[1] < floats[0]; break; case VALUE_TYPE_INT: res = values[1] < values[0]; break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), res); interpretPushShort(program, VALUE_TYPE_INT); } // 0x469858 static void op_greater(Program* program) { int arg; opcode_t type[2]; int value[2]; float* floats = (float*)&value; char text[2][80]; char* str_ptr[2]; int res; for (arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } switch (type[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, type[1], value[1]); switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, type[0], value[0]); break; case VALUE_TYPE_FLOAT: sprintf(text[0], "%.5f", floats[0]); str_ptr[0] = text[0]; break; case VALUE_TYPE_INT: sprintf(text[0], "%d", value[0]); str_ptr[0] = text[0]; break; default: assert(false && "Should be unreachable"); } res = strcmp(str_ptr[1], str_ptr[0]) > 0; break; case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%.5f", floats[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) > 0; break; case VALUE_TYPE_FLOAT: res = floats[1] > floats[0]; break; case VALUE_TYPE_INT: res = floats[1] > (float)value[0]; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: sprintf(text[1], "%d", value[1]); str_ptr[1] = text[1]; str_ptr[0] = interpretGetString(program, type[0], value[0]); res = strcmp(str_ptr[1], str_ptr[0]) > 0; break; case VALUE_TYPE_FLOAT: res = (float)value[1] > floats[0]; break; case VALUE_TYPE_INT: res = value[1] > value[0]; break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), res); interpretPushShort(program, VALUE_TYPE_INT); } // 0x469BC4 static void op_add(Program* program) { // TODO: Check everything, too many conditions, variables and allocations. opcode_t opcodes[2]; int values[2]; float* floats = (float*)&values; char* str_ptr[2]; char* t; float resf; // NOTE: original code does not use loop for (int arg = 0; arg < 2; arg++) { opcodes[arg] = popShortStack(program->stack, &(program->stackPointer)); values[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcodes[arg], values[arg]); } } switch (opcodes[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[1] = interpretGetString(program, opcodes[1], values[1]); switch (opcodes[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: t = interpretGetString(program, opcodes[0], values[0]); str_ptr[0] = (char*)mymalloc(strlen(t) + 1, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1002 strcpy(str_ptr[0], t); break; case VALUE_TYPE_FLOAT: str_ptr[0] = (char*)mymalloc(80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1011 sprintf(str_ptr[0], "%.5f", floats[0]); break; case VALUE_TYPE_INT: str_ptr[0] = (char*)mymalloc(80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1007 sprintf(str_ptr[0], "%d", values[0]); break; } t = (char*)mymalloc(strlen(str_ptr[1]) + strlen(str_ptr[0]) + 1, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1015 strcpy(t, str_ptr[1]); strcat(t, str_ptr[0]); pushLongStack(program->stack, &(program->stackPointer), interpretAddString(program, t)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); myfree(str_ptr[0], __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1019 myfree(t, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1020 break; case VALUE_TYPE_FLOAT: switch (opcodes[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, opcodes[0], values[0]); t = (char*)mymalloc(strlen(str_ptr[0]) + 80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1039 sprintf(t, "%.5f", floats[1]); strcat(t, str_ptr[0]); pushLongStack(program->stack, &(program->stackPointer), interpretAddString(program, t)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); myfree(t, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1044 break; case VALUE_TYPE_FLOAT: resf = floats[1] + floats[0]; pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; case VALUE_TYPE_INT: resf = floats[1] + (float)values[0]; pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; } break; case VALUE_TYPE_INT: switch (opcodes[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: str_ptr[0] = interpretGetString(program, opcodes[0], values[0]); t = (char*)mymalloc(strlen(str_ptr[0]) + 80, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1070 sprintf(t, "%d", values[1]); strcat(t, str_ptr[0]); pushLongStack(program->stack, &(program->stackPointer), interpretAddString(program, t)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); myfree(t, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 1075 break; case VALUE_TYPE_FLOAT: resf = (float)values[1] + floats[0]; pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; case VALUE_TYPE_INT: if ((values[0] <= 0 || (INT_MAX - values[0]) > values[1]) && (values[0] >= 0 || (INT_MIN - values[0]) <= values[1])) { pushLongStack(program->stack, &(program->stackPointer), values[1] + values[0]); interpretPushShort(program, VALUE_TYPE_INT); } else { resf = (float)values[1] + (float)values[0]; pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); } break; } break; } } // 0x46A1D8 static void op_sub(Program* program) { opcode_t type[2]; int value[2]; float* floats = (float*)&value; float resf; for (int arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } switch (type[1]) { case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_FLOAT: resf = floats[1] - floats[0]; break; default: resf = floats[1] - value[0]; break; } pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_FLOAT: resf = value[1] - floats[0]; pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; default: pushLongStack(program->stack, &(program->stackPointer), value[1] - value[0]); interpretPushShort(program, VALUE_TYPE_INT); break; } break; } } // 0x46A300 static void op_mul(Program* program) { int arg; opcode_t type[2]; int value[2]; float* floats = (float*)&value; float resf; for (arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } switch (type[1]) { case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_FLOAT: resf = floats[1] * floats[0]; break; default: resf = floats[1] * value[0]; break; } pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_FLOAT: resf = value[1] * floats[0]; pushLongStack(program->stack, &(program->stackPointer), *(int*)&resf); interpretPushShort(program, VALUE_TYPE_FLOAT); break; default: pushLongStack(program->stack, &(program->stackPointer), value[0] * value[1]); interpretPushShort(program, VALUE_TYPE_INT); break; } break; } } // 0x46A424 static void op_div(Program* program) { // TODO: Check entire function, probably errors due to casts. opcode_t type[2]; int value[2]; float* float_value = (float*)&value; float divisor; float result; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } switch (type[1]) { case VALUE_TYPE_FLOAT: if (type[0] == VALUE_TYPE_FLOAT) { divisor = float_value[0]; } else { divisor = (float)value[0]; } // NOTE: Original code is slightly different, it performs bitwise and // with 0x7FFFFFFF in order to determine if it's zero. Probably some // kind of compiler optimization. if (divisor == 0.0) { interpretError("Division (DIV) by zero"); } result = float_value[1] / divisor; pushLongStack(program->stack, &(program->stackPointer), *(int*)&result); interpretPushShort(program, VALUE_TYPE_FLOAT); break; case VALUE_TYPE_INT: if (type[0] == VALUE_TYPE_FLOAT) { divisor = float_value[0]; // NOTE: Same as above. if (divisor == 0.0) { interpretError("Division (DIV) by zero"); } result = (float)value[1] / divisor; pushLongStack(program->stack, &(program->stackPointer), *(int*)&result); interpretPushShort(program, VALUE_TYPE_FLOAT); } else { if (value[0] == 0) { interpretError("Division (DIV) by zero"); } pushLongStack(program->stack, &(program->stackPointer), value[1] / value[0]); interpretPushShort(program, VALUE_TYPE_INT); } break; } } // 0x46A5B8 static void op_mod(Program* program) { opcode_t type[2]; int value[2]; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } if (type[1] == VALUE_TYPE_FLOAT) { interpretError("Trying to MOD a float"); } if (type[1] != VALUE_TYPE_INT) { return; } if (type[0] == VALUE_TYPE_FLOAT) { interpretError("Trying to MOD with a float"); } if (value[0] == 0) { interpretError("Division (MOD) by zero"); } pushLongStack(program->stack, &(program->stackPointer), value[1] % value[0]); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46A6B4 static void op_and(Program* program) { opcode_t type[2]; int value[2]; int result; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } switch (type[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: result = 1; break; case VALUE_TYPE_FLOAT: result = (value[0] & 0x7FFFFFFF) != 0; break; case VALUE_TYPE_INT: result = value[0] != 0; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: result = value[1] != 0; break; case VALUE_TYPE_FLOAT: result = (value[1] & 0x7FFFFFFF) && (value[0] & 0x7FFFFFFF); break; case VALUE_TYPE_INT: result = (value[1] & 0x7FFFFFFF) && (value[0] != 0); break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: result = value[1] != 0; break; case VALUE_TYPE_FLOAT: result = (value[1] != 0) && (value[0] & 0x7FFFFFFF); break; case VALUE_TYPE_INT: result = (value[1] != 0) && (value[0] != 0); break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46A8D8 static void op_or(Program* program) { opcode_t type[2]; int value[2]; int result; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } switch (type[1]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: case VALUE_TYPE_FLOAT: case VALUE_TYPE_INT: result = 1; break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: result = 1; break; case VALUE_TYPE_FLOAT: result = (value[1] & 0x7FFFFFFF) || (value[0] & 0x7FFFFFFF); break; case VALUE_TYPE_INT: result = (value[1] & 0x7FFFFFFF) || (value[0] != 0); break; default: assert(false && "Should be unreachable"); } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_STRING: case VALUE_TYPE_DYNAMIC_STRING: result = 1; break; case VALUE_TYPE_FLOAT: result = (value[1] != 0) || (value[0] & 0x7FFFFFFF); break; case VALUE_TYPE_INT: result = (value[1] != 0) || (value[0] != 0); break; default: assert(false && "Should be unreachable"); } break; default: assert(false && "Should be unreachable"); } pushLongStack(program->stack, &(program->stackPointer), result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46AACC static void op_not(Program* program) { opcode_t type; int value; type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } interpretPushLong(program, value == 0); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46AB2C static void op_negate(Program* program) { opcode_t type; int value; type = popShortStack(program->stack, &(program->stackPointer)); value = popLongStack(program->stack, &(program->stackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } pushLongStack(program->stack, &(program->stackPointer), -value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46AB84 static void op_bwnot(Program* program) { opcode_t type; int value; type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } interpretPushLong(program, ~value); interpretPushShort(program, VALUE_TYPE_INT); } // floor // 0x46ABDC static void op_floor(Program* program) { opcode_t type = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, data); } if (type == VALUE_TYPE_STRING) { interpretError("Invalid arg given to floor()"); } else if (type == VALUE_TYPE_FLOAT) { type = VALUE_TYPE_INT; data = (int)(*((float*)&data)); } pushLongStack(program->stack, &(program->stackPointer), data); interpretPushShort(program, type); } // 0x46AC78 static void op_bwand(Program* program) { opcode_t type[2]; int value[2]; int result; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } switch (type[1]) { case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_FLOAT: result = (int)(float)value[1] & (int)(float)value[0]; break; default: result = (int)(float)value[1] & value[0]; break; } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_FLOAT: result = value[1] & (int)(float)value[0]; break; default: result = value[1] & value[0]; break; } break; default: return; } pushLongStack(program->stack, &(program->stackPointer), result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46ADA4 static void op_bwor(Program* program) { opcode_t type[2]; int value[2]; int result; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } switch (type[1]) { case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_FLOAT: result = (int)(float)value[1] | (int)(float)value[0]; break; default: result = (int)(float)value[1] | value[0]; break; } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_FLOAT: result = value[1] | (int)(float)value[0]; break; default: result = value[1] | value[0]; break; } break; default: return; } pushLongStack(program->stack, &(program->stackPointer), result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46AED0 static void op_bwxor(Program* program) { opcode_t type[2]; int value[2]; int result; type[0] = popShortStack(program->stack, &(program->stackPointer)); value[0] = popLongStack(program->stack, &(program->stackPointer)); if (type[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[0], value[0]); } type[1] = popShortStack(program->stack, &(program->stackPointer)); value[1] = popLongStack(program->stack, &(program->stackPointer)); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[1], value[1]); } switch (type[1]) { case VALUE_TYPE_FLOAT: switch (type[0]) { case VALUE_TYPE_FLOAT: result = (int)(float)value[1] ^ (int)(float)value[0]; break; default: result = (int)(float)value[1] ^ value[0]; break; } break; case VALUE_TYPE_INT: switch (type[0]) { case VALUE_TYPE_FLOAT: result = value[1] ^ (int)(float)value[0]; break; default: result = value[1] ^ value[0]; break; } break; default: return; } pushLongStack(program->stack, &(program->stackPointer), result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46AFFC static void op_swapa(Program* program) { opcode_t v1; int v5; opcode_t a2; int v10; v1 = rPopShort(program); v5 = popLongStack(program->returnStack, &(program->returnStackPointer)); a2 = rPopShort(program); v10 = popLongStack(program->returnStack, &(program->returnStackPointer)); pushLongStack(program->returnStack, &(program->returnStackPointer), v5); rPushShort(program, v1); pushLongStack(program->returnStack, &(program->returnStackPointer), v10); rPushShort(program, a2); } // 0x46B070 static void op_critical_done(Program* program) { program->flags &= ~PROGRAM_FLAG_CRITICAL_SECTION; } // 0x46B078 static void op_critical_start(Program* program) { program->flags |= PROGRAM_FLAG_CRITICAL_SECTION; } // 0x46B080 static void op_jmp(Program* program) { opcode_t type; int value; char err[260]; type = popShortStack(program->stack, &(program->stackPointer)); value = popLongStack(program->stack, &(program->stackPointer)); // NOTE: comparing shorts (0x46B0B1) if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } // NOTE: comparing ints (0x46B0D3) if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { sprintf(err, "Invalid type given to jmp, %x", value); interpretError(err); } program->instructionPointer = value; } // 0x46B108 static void op_call(Program* program) { opcode_t type; int data; opcode_t argumentType; opcode_t argumentValue; unsigned char* procedurePtr; int procedureFlags; char* procedureIdentifier; Program* externalProgram; int externalProcedureAddress; int externalProcedureArgumentCount; Program tempProgram; char err[256]; type = popShortStack(program->stack, &(program->stackPointer)); data = popLongStack(program->stack, &(program->stackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, data); } if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid address given to call"); } procedurePtr = program->procedures + 4 + sizeof(Procedure) * data; procedureFlags = fetchLong(procedurePtr, 4); if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) { procedureIdentifier = interpretGetName(program, fetchLong(procedurePtr, 0)); externalProgram = exportFindProcedure(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount); if (externalProgram == NULL) { interpretError("External procedure %s not found", procedureIdentifier); } type = popShortStack(program->stack, &(program->stackPointer)); data = popLongStack(program->stack, &(program->stackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, data); } if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_INT || data != externalProcedureArgumentCount) { sprintf(err, "Wrong number of arguments to external procedure %s.Expecting %d, got %d.", procedureIdentifier, externalProcedureArgumentCount, data); } rPushLong(externalProgram, program->instructionPointer); rPushShort(externalProgram, VALUE_TYPE_INT); rPushLong(externalProgram, program->flags); rPushShort(externalProgram, VALUE_TYPE_INT); rPushLong(externalProgram, (int)program->checkWaitFunc); rPushShort(externalProgram, VALUE_TYPE_INT); rPushLong(externalProgram, (int)program); rPushShort(externalProgram, VALUE_TYPE_INT); rPushLong(externalProgram, 36); rPushShort(externalProgram, VALUE_TYPE_INT); interpretPushLong(externalProgram, externalProgram->flags); interpretPushShort(externalProgram, VALUE_TYPE_INT); interpretPushLong(externalProgram, (int)externalProgram->checkWaitFunc); interpretPushShort(externalProgram, VALUE_TYPE_INT); interpretPushLong(externalProgram, externalProgram->windowId); interpretPushShort(externalProgram, VALUE_TYPE_INT); externalProgram->windowId = program->windowId; tempProgram.stackPointer = 0; tempProgram.returnStackPointer = 0; while (data-- != 0) { argumentType = interpretPopShort(program); argumentValue = interpretPopLong(program); if (argumentType == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, argumentType, argumentValue); } interpretPushLong(&tempProgram, argumentValue); interpretPushShort(&tempProgram, argumentType); } while (data++ < externalProcedureArgumentCount) { argumentType = interpretPopShort(&tempProgram); argumentValue = interpretPopLong(&tempProgram); if (argumentType == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(&tempProgram, argumentType, argumentValue); } interpretPushLong(externalProgram, argumentValue); interpretPushShort(externalProgram, argumentType); } interpretPushLong(externalProgram, externalProcedureArgumentCount); interpretPushShort(externalProgram, VALUE_TYPE_INT); program->flags |= PROGRAM_FLAG_0x20; externalProgram->flags = 0; externalProgram->instructionPointer = externalProcedureAddress; if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0 || (program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0) { // NOTE: Uninline. op_critical_start(externalProgram); } } else { program->instructionPointer = fetchLong(procedurePtr, 16); if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) { // NOTE: Uninline. op_critical_start(program); } } } // 0x46B590 static void op_pop_flags(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = popShortStack(program->stack, &(program->stackPointer)); data[arg] = popLongStack(program->stack, &(program->stackPointer)); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } program->windowId = data[0]; program->checkWaitFunc = (InterpretCheckWaitFunc*)data[1]; program->flags = data[2] & 0xFFFF; } // pop stack 2 -> set program address // 0x46B63C static void op_pop_return(Program* program) { rPopShort(program); program->instructionPointer = popLongStack(program->returnStack, &(program->returnStackPointer)); } // 0x46B658 static void op_pop_exit(Program* program) { rPopShort(program); program->instructionPointer = popLongStack(program->returnStack, &(program->returnStackPointer)); program->flags |= PROGRAM_FLAG_0x40; } // 0x46B67C static void op_pop_flags_return(Program* program) { op_pop_flags(program); rPopShort(program); program->instructionPointer = rPopLong(program); } // 0x46B698 static void op_pop_flags_exit(Program* program) { op_pop_flags(program); rPopShort(program); program->instructionPointer = rPopLong(program); program->flags |= PROGRAM_FLAG_0x40; } // 0x46B6BC static void op_pop_flags_return_val_exit(Program* program) { opcode_t type; int value; type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } op_pop_flags(program); rPopShort(program); program->instructionPointer = rPopLong(program); program->flags |= PROGRAM_FLAG_0x40; interpretPushLong(program, value); interpretPushShort(program, type); } // 0x46B73C static void op_pop_flags_return_val_exit_extern(Program* program) { opcode_t type; int value; Program* v1; type = popShortStack(program->stack, &(program->stackPointer)); value = popLongStack(program->stack, &(program->stackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } op_pop_flags(program); rPopShort(program); v1 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v1->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v1->flags = popLongStack(program->returnStack, &(program->returnStackPointer)); program->instructionPointer = rPopLong(program); program->flags |= PROGRAM_FLAG_0x40; pushLongStack(program->stack, &(program->stackPointer), value); interpretPushShort(program, type); } // 0x46B808 static void op_pop_flags_return_extern(Program* program) { Program* v1; op_pop_flags(program); rPopShort(program); v1 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v1->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v1->flags = popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); program->instructionPointer = rPopLong(program); } // 0x46B86C static void op_pop_flags_exit_extern(Program* program) { Program* v1; op_pop_flags(program); rPopShort(program); v1 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v1->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v1->flags = popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); program->instructionPointer = rPopLong(program); program->flags |= 0x40; } // pop value from stack 1 and push it to script popped from stack 2 // 0x46B8D8 static void op_pop_flags_return_val_extern(Program* program) { opcode_t type; int value; Program* v10; char* str; type = popShortStack(program->stack, &(program->stackPointer)); value = popLongStack(program->stack, &(program->stackPointer)); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } op_pop_flags(program); rPopShort(program); v10 = (Program*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v10->checkWaitFunc = (InterpretCheckWaitFunc*)popLongStack(program->returnStack, &(program->returnStackPointer)); rPopShort(program); v10->flags = popLongStack(program->returnStack, &(program->returnStackPointer)); if ((type & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { str = interpretGetString(program, type, value); pushLongStack(v10->stack, &(v10->stackPointer), interpretAddString(v10, str)); type = VALUE_TYPE_DYNAMIC_STRING; } else { pushLongStack(v10->stack, &(v10->stackPointer), value); } interpretPushShort(v10, type); if (v10->flags & 0x80) { program->flags &= ~0x80; } rPopShort(program); program->instructionPointer = rPopLong(program); rPopShort(v10); v10->instructionPointer = rPopLong(program); } // 0x46BA10 static void op_pop_address(Program* program) { rPopShort(program); rPopLong(program); } // 0x46BA2C static void op_a_to_d(Program* program) { opcode_t opcode = rPopShort(program); int data = popLongStack(program->returnStack, &(program->returnStackPointer)); pushLongStack(program->stack, &(program->stackPointer), data); interpretPushShort(program, opcode); } // 0x46BA68 static void op_d_to_a(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } pushLongStack(program->returnStack, &(program->returnStackPointer), data); rPushShort(program, opcode); } // 0x46BAC0 static void op_exit_prog(Program* program) { program->flags |= PROGRAM_FLAG_EXITED; } // 0x46BAC8 static void op_stop_prog(Program* program) { program->flags |= PROGRAM_FLAG_STOPPED; } // 0x46BAD0 static void op_fetch_global(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } // TODO: Check. int addr = program->basePointer + 6 * data; int v8 = fetchLong(program->stack, addr); opcode_t varType = fetchWord(program->stack, addr + 4); interpretPushLong(program, v8); // TODO: Check. interpretPushShort(program, varType); } // 0x46BB5C static void op_store_global(Program* program) { opcode_t type[2]; int value[2]; for (int arg = 0; arg < 2; arg++) { type[arg] = popShortStack(program->stack, &(program->stackPointer)); value[arg] = popLongStack(program->stack, &(program->stackPointer)); if (type[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type[arg], value[arg]); } } int var_address = program->basePointer + 6 * value[0]; opcode_t var_type = fetchWord(program->stack, var_address + 4); int var_value = fetchLong(program->stack, var_address); if (var_type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, var_type, var_value); } // TODO: Check offsets. storeLong(value[1], program->stack, var_address); // TODO: Check endianness. storeWord(type[1], program->stack, var_address + 4); if (type[1] == VALUE_TYPE_DYNAMIC_STRING) { // NOTE: Uninline. _interpretIncStringRef(program, VALUE_TYPE_DYNAMIC_STRING, value[1]); } } // 0x46BCAC static void op_swap(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loops. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } for (int arg = 0; arg < 2; arg++) { interpretPushLong(program, data[arg]); interpretPushShort(program, opcode[arg]); } } // fetch_proc_address // 0x46BD60 static void op_fetch_proc_address(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (opcode != VALUE_TYPE_INT) { char err[256]; sprintf(err, "Invalid type given to fetch_proc_address, %x", opcode); interpretError(err); } int procedureIndex = data; int address = fetchLong(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 16); pushLongStack(program->stack, &(program->stackPointer), address); interpretPushShort(program, VALUE_TYPE_INT); } // Pops value from stack and throws it away. // // 0x46BE10 static void op_pop(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } } // 0x46BE4C static void op_dup(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } interpretPushLong(program, data); interpretPushShort(program, opcode); interpretPushLong(program, data); interpretPushShort(program, opcode); } // 0x46BEC8 static void op_store_external(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: Original code does not use loop. for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } const char* identifier = interpretGetName(program, data[0]); if (exportStoreVariable(program, identifier, opcode[1], data[1])) { char err[256]; sprintf(err, "External variable %s does not exist\n", identifier); interpretError(err); } } // 0x46BF90 static void op_fetch_external(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } const char* identifier = interpretGetName(program, data); opcode_t variableOpcode; int variableData; if (exportFetchVariable(program, identifier, &variableOpcode, &variableData) != 0) { char err[256]; sprintf(err, "External variable %s does not exist\n", identifier); interpretError(err); } interpretPushLong(program, variableData); interpretPushShort(program, variableOpcode); } // 0x46C044 static void op_export_proc(Program* program) { opcode_t type; int value; int proc_index; unsigned char* proc_ptr; char* v9; int v10; char err[256]; type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } proc_index = value; type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } proc_ptr = program->procedures + 4 + sizeof(Procedure) * proc_index; v9 = (char*)(program->identifiers + fetchLong(proc_ptr, 0)); v10 = fetchLong(proc_ptr, 16); if (exportExportProcedure(program, v9, v10, value) != 0) { sprintf(err, "Error exporting procedure %s", v9); interpretError(err); } } // 0x46C120 static void op_export_var(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if (exportExportVariable(program, interpretGetName(program, data))) { char err[256]; sprintf(err, "External variable %s already exists", interpretGetName(program, data)); interpretError(err); } } // 0x46C1A0 static void op_exit(Program* program) { program->flags |= PROGRAM_FLAG_EXITED; Program* parent = program->parent; if (parent != NULL) { if ((parent->flags & PROGRAM_FLAG_0x0100) != 0) { parent->flags &= ~PROGRAM_FLAG_0x0100; } } if (!program->exited) { removeProgramReferences(program); program->exited = true; } } // 0x46C1EC static void op_detach(Program* program) { Program* parent = program->parent; if (parent == NULL) { return; } parent->flags &= ~PROGRAM_FLAG_0x20; parent->flags &= ~PROGRAM_FLAG_0x0100; if (parent->child == program) { parent->child = NULL; } } // callstart // 0x46C218 static void op_callstart(Program* program) { opcode_t type; int value; char* name; char err[260]; if (program->child) { interpretError("Error, already have a child process\n"); } type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to callstart"); } program->flags |= PROGRAM_FLAG_0x20; name = interpretGetString(program, type, value); // NOTE: Uninline. program->child = runScript(name); if (program->child == NULL) { sprintf(err, "Error spawning child %s", interpretGetString(program, type, value)); interpretError(err); } program->child->parent = program; program->child->windowId = program->windowId; } // spawn // 0x46C344 static void op_spawn(Program* program) { opcode_t type; int value; char* name; char err[256]; if (program->child) { interpretError("Error, already have a child process\n"); } type = interpretPopShort(program); value = interpretPopLong(program); if (type == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, type, value); } if ((type & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Invalid type given to spawn"); } program->flags |= PROGRAM_FLAG_0x0100; if ((type >> 8) & 8) { name = (char*)program->dynamicStrings + 4 + value; } else if ((type >> 8) & 16) { name = (char*)program->staticStrings + 4 + value; } else { name = NULL; } // NOTE: Uninline. program->child = runScript(name); if (program->child == NULL) { sprintf(err, "Error spawning child %s", interpretGetString(program, type, value)); interpretError(err); } program->child->parent = program; program->child->windowId = program->windowId; if ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0) { program->child->flags |= PROGRAM_FLAG_CRITICAL_SECTION; interpret(program->child, -1); } } // fork // 0x46C490 static Program* op_fork_helper(Program* program) { opcode_t opcode = popShortStack(program->stack, &(program->stackPointer)); int data = popLongStack(program->stack, &(program->stackPointer)); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } char* name = interpretGetString(program, opcode, data); Program* forked = runScript(name); if (forked == NULL) { char err[256]; sprintf(err, "couldn't fork script '%s'", interpretGetString(program, opcode, data)); interpretError(err); } forked->windowId = program->windowId; return forked; } // NOTE: Uncollapsed 0x46C490 with different signature. // // 0x46C490 static void op_fork(Program* program) { op_fork_helper(program); } // 0x46C574 static void op_exec(Program* program) { Program* parent = program->parent; Program* fork = op_fork_helper(program); if (parent != NULL) { fork->parent = parent; parent->child = fork; } fork->child = NULL; program->parent = NULL; program->flags |= PROGRAM_FLAG_EXITED; // probably inlining due to check for null parent = program->parent; if (parent != NULL) { if ((parent->flags & PROGRAM_FLAG_0x0100) != 0) { parent->flags &= ~PROGRAM_FLAG_0x0100; } } purgeProgram(program); } // 0x46C5D8 static void op_check_arg_count(Program* program) { opcode_t opcode[2]; int data[2]; // NOTE: original code does not use loop for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } int expectedArgumentCount = data[0]; int procedureIndex = data[1]; int actualArgumentCount = fetchLong(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 20); if (actualArgumentCount != expectedArgumentCount) { const char* identifier = interpretGetName(program, fetchLong(program->procedures + 4 + sizeof(Procedure) * procedureIndex, 0)); char err[260]; sprintf(err, "Wrong number of args to procedure %s\n", identifier); interpretError(err); } } // lookup_string_proc // 0x46C6B4 static void op_lookup_string_proc(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("Wrong type given to lookup_string_proc\n"); } const char* procedureNameToLookup = interpretGetString(program, opcode, data); int procedureCount = fetchLong(program->procedures, 0); // Skip procedure count (4 bytes) and main procedure, which cannot be // looked up. unsigned char* procedurePtr = program->procedures + 4 + sizeof(Procedure); // Start with 1 since we've skipped main procedure, which is always at // index 0. for (int index = 1; index < procedureCount; index++) { int offset = fetchLong(procedurePtr, 0); const char* procedureName = interpretGetName(program, offset); if (stricmp(procedureName, procedureNameToLookup) == 0) { interpretPushLong(program, index); interpretPushShort(program, VALUE_TYPE_INT); return; } procedurePtr += sizeof(Procedure); } char err[260]; sprintf(err, "Couldn't find string procedure %s\n", procedureNameToLookup); interpretError(err); } // 0x46C7DC void initInterpreter() { enabled = 1; // NOTE: The original code has different sorting. interpretAddFunc(OPCODE_NOOP, op_noop); interpretAddFunc(OPCODE_PUSH, op_const); interpretAddFunc(OPCODE_ENTER_CRITICAL_SECTION, op_critical_start); interpretAddFunc(OPCODE_LEAVE_CRITICAL_SECTION, op_critical_done); interpretAddFunc(OPCODE_JUMP, op_jmp); interpretAddFunc(OPCODE_CALL, op_call); interpretAddFunc(OPCODE_CALL_AT, op_call_at); interpretAddFunc(OPCODE_CALL_WHEN, op_call_condition); interpretAddFunc(OPCODE_CALLSTART, op_callstart); interpretAddFunc(OPCODE_EXEC, op_exec); interpretAddFunc(OPCODE_SPAWN, op_spawn); interpretAddFunc(OPCODE_FORK, op_fork); interpretAddFunc(OPCODE_A_TO_D, op_a_to_d); interpretAddFunc(OPCODE_D_TO_A, op_d_to_a); interpretAddFunc(OPCODE_EXIT, op_exit); interpretAddFunc(OPCODE_DETACH, op_detach); interpretAddFunc(OPCODE_EXIT_PROGRAM, op_exit_prog); interpretAddFunc(OPCODE_STOP_PROGRAM, op_stop_prog); interpretAddFunc(OPCODE_FETCH_GLOBAL, op_fetch_global); interpretAddFunc(OPCODE_STORE_GLOBAL, op_store_global); interpretAddFunc(OPCODE_FETCH_EXTERNAL, op_fetch_external); interpretAddFunc(OPCODE_STORE_EXTERNAL, op_store_external); interpretAddFunc(OPCODE_EXPORT_VARIABLE, op_export_var); interpretAddFunc(OPCODE_EXPORT_PROCEDURE, op_export_proc); interpretAddFunc(OPCODE_SWAP, op_swap); interpretAddFunc(OPCODE_SWAPA, op_swapa); interpretAddFunc(OPCODE_POP, op_pop); interpretAddFunc(OPCODE_DUP, op_dup); interpretAddFunc(OPCODE_POP_RETURN, op_pop_return); interpretAddFunc(OPCODE_POP_EXIT, op_pop_exit); interpretAddFunc(OPCODE_POP_ADDRESS, op_pop_address); interpretAddFunc(OPCODE_POP_FLAGS, op_pop_flags); interpretAddFunc(OPCODE_POP_FLAGS_RETURN, op_pop_flags_return); interpretAddFunc(OPCODE_POP_FLAGS_EXIT, op_pop_flags_exit); interpretAddFunc(OPCODE_POP_FLAGS_RETURN_EXTERN, op_pop_flags_return_extern); interpretAddFunc(OPCODE_POP_FLAGS_EXIT_EXTERN, op_pop_flags_exit_extern); interpretAddFunc(OPCODE_POP_FLAGS_RETURN_VAL_EXTERN, op_pop_flags_return_val_extern); interpretAddFunc(OPCODE_POP_FLAGS_RETURN_VAL_EXIT, op_pop_flags_return_val_exit); interpretAddFunc(OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN, op_pop_flags_return_val_exit_extern); interpretAddFunc(OPCODE_CHECK_PROCEDURE_ARGUMENT_COUNT, op_check_arg_count); interpretAddFunc(OPCODE_LOOKUP_PROCEDURE_BY_NAME, op_lookup_string_proc); interpretAddFunc(OPCODE_POP_BASE, op_pop_base); interpretAddFunc(OPCODE_POP_TO_BASE, op_pop_to_base); interpretAddFunc(OPCODE_PUSH_BASE, op_push_base); interpretAddFunc(OPCODE_SET_GLOBAL, op_set_global); interpretAddFunc(OPCODE_FETCH_PROCEDURE_ADDRESS, op_fetch_proc_address); interpretAddFunc(OPCODE_DUMP, op_dump); interpretAddFunc(OPCODE_IF, op_if); interpretAddFunc(OPCODE_WHILE, op_while); interpretAddFunc(OPCODE_STORE, op_store); interpretAddFunc(OPCODE_FETCH, op_fetch); interpretAddFunc(OPCODE_EQUAL, op_equal); interpretAddFunc(OPCODE_NOT_EQUAL, op_not_equal); interpretAddFunc(OPCODE_LESS_THAN_EQUAL, op_less_equal); interpretAddFunc(OPCODE_GREATER_THAN_EQUAL, op_greater_equal); interpretAddFunc(OPCODE_LESS_THAN, op_less); interpretAddFunc(OPCODE_GREATER_THAN, op_greater); interpretAddFunc(OPCODE_ADD, op_add); interpretAddFunc(OPCODE_SUB, op_sub); interpretAddFunc(OPCODE_MUL, op_mul); interpretAddFunc(OPCODE_DIV, op_div); interpretAddFunc(OPCODE_MOD, op_mod); interpretAddFunc(OPCODE_AND, op_and); interpretAddFunc(OPCODE_OR, op_or); interpretAddFunc(OPCODE_BITWISE_AND, op_bwand); interpretAddFunc(OPCODE_BITWISE_OR, op_bwor); interpretAddFunc(OPCODE_BITWISE_XOR, op_bwxor); interpretAddFunc(OPCODE_BITWISE_NOT, op_bwnot); interpretAddFunc(OPCODE_FLOOR, op_floor); interpretAddFunc(OPCODE_NOT, op_not); interpretAddFunc(OPCODE_NEGATE, op_negate); interpretAddFunc(OPCODE_WAIT, op_wait); interpretAddFunc(OPCODE_CANCEL, op_cancel); interpretAddFunc(OPCODE_CANCEL_ALL, op_cancelall); interpretAddFunc(OPCODE_START_CRITICAL, op_critical_start); interpretAddFunc(OPCODE_END_CRITICAL, op_critical_done); initIntlib(); initExport(); } // 0x46CC68 void interpretClose() { exportClose(); intlibClose(); } // NOTE: Unused. // // 0x46CC74 void interpretEnableInterpreter(int value) { enabled = value; if (value) { // NOTE: Uninline. interpretResumeEvents(); } else { // NOTE: Uninline. interpretSuspendEvents(); } } // 0x46CCA4 void interpret(Program* program, int a2) { // 0x59E798 static int busy; char err[260]; Program* oldCurrentProgram = currentProgram; if (!enabled) { return; } if (busy) { return; } if (program->exited || (program->flags & PROGRAM_FLAG_0x20) != 0 || (program->flags & PROGRAM_FLAG_0x0100) != 0) { return; } if (program->field_78 == -1) { program->field_78 = 1000 * timerFunc() / timerTick; } currentProgram = program; if (setjmp(program->env)) { currentProgram = oldCurrentProgram; program->flags |= PROGRAM_FLAG_EXITED | PROGRAM_FLAG_0x04; return; } if ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0 && a2 < 3) { a2 = 3; } while ((program->flags & PROGRAM_FLAG_CRITICAL_SECTION) != 0 || --a2 != -1) { if ((program->flags & (PROGRAM_FLAG_EXITED | PROGRAM_FLAG_0x04 | PROGRAM_FLAG_STOPPED | PROGRAM_FLAG_0x20 | PROGRAM_FLAG_0x40 | PROGRAM_FLAG_0x0100)) != 0) { break; } if (program->exited) { break; } if ((program->flags & PROGRAM_IS_WAITING) != 0) { busy = 1; if (program->checkWaitFunc != NULL) { if (!program->checkWaitFunc(program)) { busy = 0; continue; } } busy = 0; program->checkWaitFunc = NULL; program->flags &= ~PROGRAM_IS_WAITING; } // NOTE: Uninline. opcode_t opcode = getOp(program); // TODO: Replace with field_82 and field_80? program->flags &= 0xFFFF; program->flags |= (opcode << 16); if (!((opcode >> 8) & 0x80)) { sprintf(err, "Bad opcode %x %c %d.", opcode, opcode, opcode); interpretError(err); } unsigned int opcodeIndex = opcode & 0x3FF; OpcodeHandler* handler = opTable[opcodeIndex]; if (handler == NULL) { sprintf(err, "Undefined opcode %x.", opcode); interpretError(err); } handler(program); } if ((program->flags & PROGRAM_FLAG_EXITED) != 0) { if (program->parent != NULL) { if (program->parent->flags & PROGRAM_FLAG_0x20) { program->parent->flags &= ~PROGRAM_FLAG_0x20; program->parent->child = NULL; program->parent = NULL; } } } program->flags &= ~PROGRAM_FLAG_0x40; currentProgram = oldCurrentProgram; checkProgramStrings(program); } // Prepares program stacks for executing proc at [address]. // // 0x46CED0 static void setupCallWithReturnVal(Program* program, int address, int returnAddress) { // Save current instruction pointer pushLongStack(program->returnStack, &(program->returnStackPointer), program->instructionPointer); rPushShort(program, VALUE_TYPE_INT); // Save return address pushLongStack(program->returnStack, &(program->returnStackPointer), returnAddress); rPushShort(program, VALUE_TYPE_INT); // Save program flags pushLongStack(program->stack, &(program->stackPointer), program->flags & 0xFFFF); interpretPushShort(program, VALUE_TYPE_INT); pushLongStack(program->stack, &(program->stackPointer), (intptr_t)program->checkWaitFunc); interpretPushShort(program, VALUE_TYPE_INT); pushLongStack(program->stack, &(program->stackPointer), program->windowId); interpretPushShort(program, VALUE_TYPE_INT); program->flags &= ~0xFFFF; program->instructionPointer = address; } // NOTE: Inlined. // // 0x46CF78 static void setupCall(Program* program, int address, int returnAddress) { setupCallWithReturnVal(program, address, returnAddress); interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } // 0x46CF9C static void setupExternalCallWithReturnVal(Program* program1, Program* program2, int address, int a4) { pushLongStack(program2->returnStack, &(program2->returnStackPointer), program2->instructionPointer); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->returnStack, &(program2->returnStackPointer), program1->flags & 0xFFFF); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->returnStack, &(program2->returnStackPointer), (intptr_t)program1->checkWaitFunc); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->returnStack, &(program2->returnStackPointer), (intptr_t)program1); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->returnStack, &(program2->returnStackPointer), a4); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->stack, &(program2->stackPointer), program2->flags & 0xFFFF); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->stack, &(program2->stackPointer), (intptr_t)program2->checkWaitFunc); rPushShort(program2, VALUE_TYPE_INT); pushLongStack(program2->stack, &(program2->stackPointer), program2->windowId); rPushShort(program2, VALUE_TYPE_INT); program2->flags &= ~0xFFFF; program2->instructionPointer = address; program2->windowId = program1->windowId; program1->flags |= PROGRAM_FLAG_0x20; } // 0x46D0B0 static void setupExternalCall(Program* program1, Program* program2, int address, int a4) { setupExternalCallWithReturnVal(program1, program2, address, a4); interpretPushLong(program2, 0); interpretPushShort(program2, VALUE_TYPE_INT); } // 0x46DB58 void executeProc(Program* program, int procedureIndex) { unsigned char* procedurePtr; char* procedureIdentifier; int procedureAddress; Program* externalProgram; int externalProcedureAddress; int externalProcedureArgumentCount; int procedureFlags; char err[256]; procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex; procedureFlags = fetchLong(procedurePtr, 4); if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) { procedureIdentifier = interpretGetName(program, fetchLong(procedurePtr, 0)); externalProgram = exportFindProcedure(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount); if (externalProgram != NULL) { if (externalProcedureArgumentCount == 0) { } else { sprintf(err, "External procedure cannot take arguments in interrupt context"); interpretOutput(err); } } else { sprintf(err, "External procedure %s not found\n", procedureIdentifier); interpretOutput(err); } // NOTE: Uninline. setupExternalCall(program, externalProgram, externalProcedureAddress, 28); procedurePtr = externalProgram->procedures + 4 + sizeof(Procedure) * procedureIndex; procedureFlags = fetchLong(procedurePtr, 4); if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) { // NOTE: Uninline. op_critical_start(externalProgram); interpret(externalProgram, 0); } } else { procedureAddress = fetchLong(procedurePtr, 16); // NOTE: Uninline. setupCall(program, procedureAddress, 20); if ((procedureFlags & PROCEDURE_FLAG_CRITICAL) != 0) { // NOTE: Uninline. op_critical_start(program); interpret(program, 0); } } } // Returns index of the procedure with specified name or -1 if no such // procedure exists. // // 0x46DCD0 int interpretFindProcedure(Program* program, const char* name) { int procedureCount = fetchLong(program->procedures, 0); unsigned char* ptr = program->procedures + 4; for (int index = 0; index < procedureCount; index++) { int identifierOffset = fetchLong(ptr, offsetof(Procedure, field_0)); if (stricmp((char*)(program->identifiers + identifierOffset), name) == 0) { return index; } ptr += sizeof(Procedure); } return -1; } // 0x46DD2C void executeProcedure(Program* program, int procedureIndex) { unsigned char* procedurePtr; char* procedureIdentifier; int procedureAddress; Program* externalProgram; int externalProcedureAddress; int externalProcedureArgumentCount; int procedureFlags; char err[256]; jmp_buf env; procedurePtr = program->procedures + 4 + sizeof(Procedure) * procedureIndex; procedureFlags = fetchLong(procedurePtr, 4); if ((procedureFlags & PROCEDURE_FLAG_IMPORTED) != 0) { procedureIdentifier = interpretGetName(program, fetchLong(procedurePtr, 0)); externalProgram = exportFindProcedure(procedureIdentifier, &externalProcedureAddress, &externalProcedureArgumentCount); if (externalProgram != NULL) { if (externalProcedureArgumentCount == 0) { // NOTE: Uninline. setupExternalCall(program, externalProgram, externalProcedureAddress, 32); memcpy(env, program->env, sizeof(env)); interpret(externalProgram, -1); memcpy(externalProgram->env, env, sizeof(env)); } else { sprintf(err, "External procedure cannot take arguments in interrupt context"); interpretOutput(err); } } else { sprintf(err, "External procedure %s not found\n", procedureIdentifier); interpretOutput(err); } } else { procedureAddress = fetchLong(procedurePtr, 16); // NOTE: Uninline. setupCall(program, procedureAddress, 24); memcpy(env, program->env, sizeof(env)); interpret(program, -1); memcpy(program->env, env, sizeof(env)); } } // 0x46DEE4 static void doEvents() { ProgramListNode* programListNode; unsigned int time; int procedureCount; int procedureIndex; unsigned char* procedurePtr; int procedureFlags; int oldProgramFlags; int oldInstructionPointer; opcode_t opcode; int data; jmp_buf env; if (suspendEvents) { return; } programListNode = head; time = 1000 * timerFunc() / timerTick; while (programListNode != NULL) { procedureCount = fetchLong(programListNode->program->procedures, 0); procedurePtr = programListNode->program->procedures + 4; for (procedureIndex = 0; procedureIndex < procedureCount; procedureIndex++) { procedureFlags = fetchLong(procedurePtr, 4); if ((procedureFlags & PROCEDURE_FLAG_CONDITIONAL) != 0) { memcpy(env, programListNode->program, sizeof(env)); oldProgramFlags = programListNode->program->flags; oldInstructionPointer = programListNode->program->instructionPointer; programListNode->program->flags = 0; programListNode->program->instructionPointer = fetchLong(procedurePtr, 12); interpret(programListNode->program, -1); if ((programListNode->program->flags & PROGRAM_FLAG_0x04) == 0) { opcode = interpretPopShort(programListNode->program); data = interpretPopLong(programListNode->program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(programListNode->program, opcode, data); } programListNode->program->flags = oldProgramFlags; programListNode->program->instructionPointer = oldInstructionPointer; if (data != 0) { // NOTE: Uninline. storeLong(0, procedurePtr, 4); executeProc(programListNode->program, procedureIndex); } } memcpy(programListNode->program, env, sizeof(env)); } else if ((procedureFlags & PROCEDURE_FLAG_TIMED) != 0) { if ((unsigned int)fetchLong(procedurePtr, 8) < time) { // NOTE: Uninline. storeLong(0, procedurePtr, 4); executeProc(programListNode->program, procedureIndex); } } procedurePtr += sizeof(Procedure); } programListNode = programListNode->next; } } // 0x46E10C static void removeProgList(ProgramListNode* programListNode) { ProgramListNode* tmp; tmp = programListNode->next; if (tmp != NULL) { tmp->prev = programListNode->prev; } tmp = programListNode->prev; if (tmp != NULL) { tmp->next = programListNode->next; } else { head = programListNode->next; } interpretFreeProgram(programListNode->program); myfree(programListNode, __FILE__, __LINE__); // "..\\int\\INTRPRET.C", 2923 } // 0x46E154 static void insertProgram(Program* program) { ProgramListNode* programListNode = (ProgramListNode*)mymalloc(sizeof(*programListNode), __FILE__, __LINE__); // .\\int\\INTRPRET.C, 2907 programListNode->program = program; programListNode->next = head; programListNode->prev = NULL; if (head != NULL) { head->prev = programListNode; } head = programListNode; } // NOTE: Inlined. // // 0x46E15C void runProgram(Program* program) { program->flags |= PROGRAM_FLAG_0x02; insertProgram(program); } // NOTE: Inlined. // // 0x46E19C Program* runScript(char* name) { Program* program; // NOTE: Uninline. program = allocateProgram(interpretMangleName(name)); if (program != NULL) { // NOTE: Uninline. runProgram(program); interpret(program, 24); } return program; } // NOTE: Unused. // // 0x46E1DC void interpretSetCPUBurstSize(int value) { if (value < 1) { value = 1; } cpuBurstSize = value; } // 0x46E1EC void updatePrograms() { ProgramListNode* curr = head; while (curr != NULL) { ProgramListNode* next = curr->next; if (curr->program != NULL) { interpret(curr->program, cpuBurstSize); } if (curr->program->exited) { removeProgList(curr); } curr = next; } doEvents(); updateIntLib(); } // 0x46E238 void clearPrograms() { ProgramListNode* curr = head; while (curr != NULL) { ProgramListNode* next = curr->next; removeProgList(curr); curr = next; } } // NOTE: Unused. // // 0x46E254 void clearTopProgram() { ProgramListNode* next; next = head->next; removeProgList(head); head = next; } // NOTE: Unused. // // 0x46E26C char** getProgramList(int* programListLengthPtr) { char** programList; int programListLength; int index; int it; ProgramListNode* programListNode; index = 0; programListLength = 0; for (it = 1; it <= 2; it++) { programListNode = head; while (programListNode != NULL) { if (it == 1) { programListLength++; } else if (it == 2) { if (index < programListLength) { programList[index++] = mystrdup(programListNode->program->name, __FILE__, __LINE__); // "..\int\INTRPRET.C", 3014 } } programListNode = programListNode->next; } if (it == 1) { programList = (char**)mymalloc(sizeof(*programList) * programListLength, __FILE__, __LINE__); // "..\int\INTRPRET.C", 3021 } } if (programListLengthPtr != NULL) { *programListLengthPtr = programListLength; } return programList; } // NOTE: Unused. // // 0x46E31C void freeProgramList(char** programList, int programListLength) { int index; if (programList != NULL) { for (index = 0; index < programListLength; index++) { if (programList[index] != NULL) { myfree(programList[index], __FILE__, __LINE__); // "..\int\INTRPRET.C", 3035 } } } myfree(programList, __FILE__, __LINE__); // "..\int\INTRPRET.C", 3038 } // 0x46E368 void interpretAddFunc(int opcode, OpcodeHandler* handler) { int index = opcode & 0x3FFF; if (index >= OPCODE_MAX_COUNT) { printf("Too many opcodes!\n"); exit(1); } opTable[index] = handler; } // NOTE: Unused. // // 0x46E398 void interpretSetFilenameFunc(InterpretMangleFunc* func) { filenameFunc = func; } // NOTE: Unused. // // 0x46E3A0 void interpretSuspendEvents() { suspendEvents++; if (suspendEvents == 1) { suspendTime = timerFunc(); } } // NOTE: Unused. // // 0x46E3C0 void interpretResumeEvents() { int counter; ProgramListNode* programListNode; unsigned int time; int procedureCount; int procedureIndex; unsigned char* procedurePtr; counter = suspendEvents; if (suspendEvents != 0) { suspendEvents--; if (counter == 1) { programListNode = head; time = 1000 * (timerFunc() - suspendTime) / timerTick; while (programListNode != NULL) { procedureCount = fetchLong(programListNode->program->procedures, 0); procedurePtr = programListNode->program->procedures + 4; for (procedureIndex = 0; procedureIndex < procedureCount; procedureIndex++) { if ((fetchLong(procedurePtr, 4) & PROCEDURE_FLAG_TIMED) != 0) { storeLong(fetchLong(procedurePtr, 8) + time, procedurePtr, 8); } } programListNode = programListNode->next; procedurePtr += sizeof(Procedure); } } } } // NOTE: Unused. // // 0x46E4AC int interpretSaveProgramState() { return 0; } // 0x46E5EC void interpretDumpStringHeap() { ProgramListNode* programListNode = head; while (programListNode != NULL) { Program* program = programListNode->program; if (program != NULL) { int total = 0; if (program->dynamicStrings != NULL) { debug_printf("Program %s\n"); unsigned char* heap = program->dynamicStrings + sizeof(int); while (*(unsigned short*)heap != 0x8000) { int size = *(short*)heap; if (size >= 0) { int refcount = *(short*)(heap + sizeof(short)); debug_printf("Size: %d, ref: %d, string %s\n", size, refcount, (char*)(heap + sizeof(short) + sizeof(short))); } else { debug_printf("Free space, length %d\n", -size); } // TODO: Not sure about total, probably calculated wrong, check. heap += sizeof(short) + sizeof(short) + size; total += sizeof(short) + sizeof(short) + size; } debug_printf("Total length of heap %d, stored length %d\n", total, *(int*)(program->dynamicStrings)); } else { debug_printf("No string heap for program %s\n", program->name); } } programListNode = programListNode->next; } } ================================================ FILE: src/int/intrpret.h ================================================ #ifndef FALLOUT_INT_INTRPRET_H_ #define FALLOUT_INT_INTRPRET_H_ #include #include typedef enum Opcode { OPCODE_NOOP = 0x8000, OPCODE_PUSH = 0x8001, OPCODE_ENTER_CRITICAL_SECTION = 0x8002, OPCODE_LEAVE_CRITICAL_SECTION = 0x8003, OPCODE_JUMP = 0x8004, OPCODE_CALL = 0x8005, OPCODE_CALL_AT = 0x8006, OPCODE_CALL_WHEN = 0x8007, OPCODE_CALLSTART = 0x8008, OPCODE_EXEC = 0x8009, OPCODE_SPAWN = 0x800A, OPCODE_FORK = 0x800B, OPCODE_A_TO_D = 0x800C, OPCODE_D_TO_A = 0x800D, OPCODE_EXIT = 0x800E, OPCODE_DETACH = 0x800F, OPCODE_EXIT_PROGRAM = 0x8010, OPCODE_STOP_PROGRAM = 0x8011, OPCODE_FETCH_GLOBAL = 0x8012, OPCODE_STORE_GLOBAL = 0x8013, OPCODE_FETCH_EXTERNAL = 0x8014, OPCODE_STORE_EXTERNAL = 0x8015, OPCODE_EXPORT_VARIABLE = 0x8016, OPCODE_EXPORT_PROCEDURE = 0x8017, OPCODE_SWAP = 0x8018, OPCODE_SWAPA = 0x8019, OPCODE_POP = 0x801A, OPCODE_DUP = 0x801B, OPCODE_POP_RETURN = 0x801C, OPCODE_POP_EXIT = 0x801D, OPCODE_POP_ADDRESS = 0x801E, OPCODE_POP_FLAGS = 0x801F, OPCODE_POP_FLAGS_RETURN = 0x8020, OPCODE_POP_FLAGS_EXIT = 0x8021, OPCODE_POP_FLAGS_RETURN_EXTERN = 0x8022, OPCODE_POP_FLAGS_EXIT_EXTERN = 0x8023, OPCODE_POP_FLAGS_RETURN_VAL_EXTERN = 0x8024, OPCODE_POP_FLAGS_RETURN_VAL_EXIT = 0x8025, OPCODE_POP_FLAGS_RETURN_VAL_EXIT_EXTERN = 0x8026, OPCODE_CHECK_PROCEDURE_ARGUMENT_COUNT = 0x8027, OPCODE_LOOKUP_PROCEDURE_BY_NAME = 0x8028, OPCODE_POP_BASE = 0x8029, OPCODE_POP_TO_BASE = 0x802A, OPCODE_PUSH_BASE = 0x802B, OPCODE_SET_GLOBAL = 0x802C, OPCODE_FETCH_PROCEDURE_ADDRESS = 0x802D, OPCODE_DUMP = 0x802E, OPCODE_IF = 0x802F, OPCODE_WHILE = 0x8030, OPCODE_STORE = 0x8031, OPCODE_FETCH = 0x8032, OPCODE_EQUAL = 0x8033, OPCODE_NOT_EQUAL = 0x8034, OPCODE_LESS_THAN_EQUAL = 0x8035, OPCODE_GREATER_THAN_EQUAL = 0x8036, OPCODE_LESS_THAN = 0x8037, OPCODE_GREATER_THAN = 0x8038, OPCODE_ADD = 0x8039, OPCODE_SUB = 0x803A, OPCODE_MUL = 0x803B, OPCODE_DIV = 0x803C, OPCODE_MOD = 0x803D, OPCODE_AND = 0x803E, OPCODE_OR = 0x803F, OPCODE_BITWISE_AND = 0x8040, OPCODE_BITWISE_OR = 0x8041, OPCODE_BITWISE_XOR = 0x8042, OPCODE_BITWISE_NOT = 0x8043, OPCODE_FLOOR = 0x8044, OPCODE_NOT = 0x8045, OPCODE_NEGATE = 0x8046, OPCODE_WAIT = 0x8047, OPCODE_CANCEL = 0x8048, OPCODE_CANCEL_ALL = 0x8049, OPCODE_START_CRITICAL = 0x804A, OPCODE_END_CRITICAL = 0x804B, } Opcode; typedef enum ProcedureFlags { PROCEDURE_FLAG_TIMED = 0x01, PROCEDURE_FLAG_CONDITIONAL = 0x02, PROCEDURE_FLAG_IMPORTED = 0x04, PROCEDURE_FLAG_EXPORTED = 0x08, PROCEDURE_FLAG_CRITICAL = 0x10, } ProcedureFlags; typedef enum ProgramFlags { PROGRAM_FLAG_EXITED = 0x01, PROGRAM_FLAG_0x02 = 0x02, PROGRAM_FLAG_0x04 = 0x04, PROGRAM_FLAG_STOPPED = 0x08, // Program is in waiting state with `checkWaitFunc` set. PROGRAM_IS_WAITING = 0x10, PROGRAM_FLAG_0x20 = 0x20, PROGRAM_FLAG_0x40 = 0x40, PROGRAM_FLAG_CRITICAL_SECTION = 0x80, PROGRAM_FLAG_0x0100 = 0x0100, } ProgramFlags; enum RawValueType { RAW_VALUE_TYPE_OPCODE = 0x8000, RAW_VALUE_TYPE_INT = 0x4000, RAW_VALUE_TYPE_FLOAT = 0x2000, RAW_VALUE_TYPE_STATIC_STRING = 0x1000, RAW_VALUE_TYPE_DYNAMIC_STRING = 0x0800, }; #define VALUE_TYPE_MASK 0xF7FF #define VALUE_TYPE_INT 0xC001 #define VALUE_TYPE_FLOAT 0xA001 #define VALUE_TYPE_STRING 0x9001 #define VALUE_TYPE_DYNAMIC_STRING 0x9801 typedef unsigned short opcode_t; typedef struct Procedure { int field_0; int field_4; int field_8; int field_C; int field_10; int field_14; } Procedure; typedef struct Program Program; typedef int(InterpretCheckWaitFunc)(Program* program); // It's size in original code is 144 (0x8C) bytes due to the different // size of `jmp_buf`. typedef struct Program { char* name; unsigned char* data; struct Program* parent; struct Program* child; int instructionPointer; // current pos in data int framePointer; // saved stack 1 pos - probably beginning of local variables - probably called base int basePointer; // saved stack 1 pos - probably beginning of global variables unsigned char* stack; // stack 1 (4096 bytes) unsigned char* returnStack; // stack 2 (4096 bytes) int stackPointer; // stack pointer 1 int returnStackPointer; // stack pointer 2 unsigned char* staticStrings; // static strings table unsigned char* dynamicStrings; // dynamic strings table unsigned char* identifiers; unsigned char* procedures; jmp_buf env; unsigned int waitEnd; // end time of timer (field_74 + wait time) unsigned int waitStart; // time when wait was called int field_78; // time when program begin execution (for the first time)?, -1 - program never executed InterpretCheckWaitFunc* checkWaitFunc; int flags; // flags int windowId; bool exited; } Program; typedef char*(InterpretMangleFunc)(char* fileName); typedef int(InterpretOutputFunc)(char* string); typedef unsigned int(InterpretTimerFunc)(); typedef void(OpcodeHandler)(Program* program); void interpretSetTimeFunc(InterpretTimerFunc* timerFunc, int timerTick); char* interpretMangleName(char* fileName); void interpretOutputFunc(InterpretOutputFunc* func); int interpretOutput(const char* format, ...); void interpretError(const char* format, ...); void interpretDecStringRef(Program* program, opcode_t a2, int a3); void interpretPushShort(Program* program, int value); void interpretPushLong(Program* program, int value); opcode_t interpretPopShort(Program* program); int interpretPopLong(Program* program); void interpretFreeProgram(Program* program); Program* allocateProgram(const char* path); char* interpretGetString(Program* program, opcode_t opcode, int offset); char* interpretGetName(Program* program, int offset); int interpretAddString(Program* program, char* string); void initInterpreter(); void interpretClose(); void interpretEnableInterpreter(int enabled); void interpret(Program* program, int a2); void executeProc(Program* program, int procedureIndex); int interpretFindProcedure(Program* prg, const char* name); void executeProcedure(Program* program, int procedureIndex); void runProgram(Program* program); Program* runScript(char* name); void interpretSetCPUBurstSize(int value); void updatePrograms(); void clearPrograms(); void clearTopProgram(); char** getProgramList(int* programListLengthPtr); void freeProgramList(char** programList, int programListLength); void interpretAddFunc(int opcode, OpcodeHandler* handler); void interpretSetFilenameFunc(InterpretMangleFunc* func); void interpretSuspendEvents(); void interpretResumeEvents(); int interpretSaveProgramState(); void interpretDumpStringHeap(); #endif /* FALLOUT_INT_INTRPRET_H_ */ ================================================ FILE: src/int/memdbg.c ================================================ #include "int/memdbg.h" #include #include #include #include static void defaultOutput(const char* string); static int debug_printf(const char* format, ...); static void error(const char* func, size_t size, const char* file, int line); static void* defaultMalloc(size_t size); static void* defaultRealloc(void* ptr, size_t size); static void defaultFree(void* ptr); // 0x519588 static MemoryManagerPrintErrorProc* outputFunc = defaultOutput; // 0x51958C static MallocProc* mallocPtr = defaultMalloc; // 0x519590 static ReallocProc* reallocPtr = defaultRealloc; // 0x519594 static FreeProc* freePtr = defaultFree; // 0x4845B0 static void defaultOutput(const char* string) { printf("%s", string); } // NOTE: Unused. // // 0x4845C0 void memoryRegisterDebug(MemoryManagerPrintErrorProc* func) { outputFunc = func; } // 0x4845C8 static int debug_printf(const char* format, ...) { // 0x631F7C static char buf[256]; int length = 0; if (outputFunc != NULL) { va_list args; va_start(args, format); length = vsprintf(buf, format, args); va_end(args); outputFunc(buf); } return length; } // 0x484610 static void error(const char* func, size_t size, const char* file, int line) { debug_printf("%s: Error allocating block of size %ld (%x), %s %d\n", func, size, size, file, line); exit(1); } // 0x48462C static void* defaultMalloc(size_t size) { return malloc(size); } // 0x484634 static void* defaultRealloc(void* ptr, size_t size) { return realloc(ptr, size); } // 0x48463C static void defaultFree(void* ptr) { free(ptr); } // 0x484644 void memoryRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) { mallocPtr = mallocProc; reallocPtr = reallocProc; freePtr = freeProc; } // 0x484660 void* mymalloc(size_t size, const char* file, int line) { void* ptr = mallocPtr(size); if (ptr == NULL) { error("malloc", size, file, line); } return ptr; } // 0x4846B4 void* myrealloc(void* ptr, size_t size, const char* file, int line) { ptr = reallocPtr(ptr, size); if (ptr == NULL) { error("realloc", size, file, line); } return ptr; } // 0x484688 void myfree(void* ptr, const char* file, int line) { if (ptr == NULL) { debug_printf("free: free of a null ptr, %s %d\n", file, line); exit(1); } freePtr(ptr); } // 0x4846D8 void* mycalloc(int count, int size, const char* file, int line) { void* ptr = mallocPtr(count * size); if (ptr == NULL) { error("calloc", size, file, line); } memset(ptr, 0, count * size); return ptr; } // 0x484710 char* mystrdup(const char* string, const char* file, int line) { size_t size = strlen(string) + 1; char* copy = (char*)mallocPtr(size); if (copy == NULL) { error("strdup", size, file, line); } strcpy(copy, string); return copy; } ================================================ FILE: src/int/memdbg.h ================================================ #ifndef FALLOUT_INT_MEMDBG_H_ #define FALLOUT_INT_MEMDBG_H_ #include "memory_defs.h" typedef void(MemoryManagerPrintErrorProc)(const char* string); void memoryRegisterDebug(MemoryManagerPrintErrorProc* func); void memoryRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); void* mymalloc(size_t size, const char* file, int line); void* myrealloc(void* ptr, size_t size, const char* file, int line); void myfree(void* ptr, const char* file, int line); void* mycalloc(int count, int size, const char* file, int line); char* mystrdup(const char* string, const char* file, int line); #endif /* FALLOUT_INT_MEMDBG_H_ */ ================================================ FILE: src/int/mousemgr.c ================================================ #include "int/mousemgr.h" #include #include #include "plib/gnw/input.h" #include "int/datafile.h" #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "int/memdbg.h" #define MOUSE_MGR_CACHE_CAPACITY 32 typedef enum MouseManagerMouseType { MOUSE_MANAGER_MOUSE_TYPE_NONE, MOUSE_MANAGER_MOUSE_TYPE_STATIC, MOUSE_MANAGER_MOUSE_TYPE_ANIMATED, } MouseManagerMouseType; typedef struct MouseManagerStaticData { unsigned char* data; int field_4; int field_8; int width; int height; } MouseManagerStaticData; typedef struct MouseManagerAnimatedData { unsigned char** field_0; unsigned char** field_4; int* field_8; int* field_C; int width; int height; float field_18; int field_1C; int field_20; signed char field_24; signed char frameCount; signed char field_26; } MouseManagerAnimatedData; typedef struct MouseManagerCacheEntry { union { void* data; MouseManagerStaticData* staticData; MouseManagerAnimatedData* animatedData; }; int type; unsigned char palette[256 * 3]; int ref; char fileName[32]; char field_32C[32]; } MouseManagerCacheEntry; static char* defaultNameMangler(char* a1); static int defaultRateCallback(); static int defaultTimeCallback(); static void setShape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans); static void freeCacheEntry(MouseManagerCacheEntry* entry); static int cacheInsert(void** data, int type, unsigned char* palette, const char* fileName); static void cacheFlush(); static MouseManagerCacheEntry* cacheFind(const char* fileName, unsigned char** palettePtr, int* a3, int* a4, int* widthPtr, int* heightPtr, int* typePtr); // 0x5195A8 static MouseManagerNameMangler* mouseNameMangler = defaultNameMangler; // 0x5195AC static MouseManagerRateProvider* rateCallback = defaultRateCallback; // 0x5195B0 static MouseManagerTimeProvider* currentTimeCallback = defaultTimeCallback; // 0x5195B4 static int curref = 1; // 0x63247C static MouseManagerCacheEntry Cache[MOUSE_MGR_CACHE_CAPACITY]; // 0x638DFC static bool animating; // 0x638E00 static unsigned char* curPal; // 0x638E04 static MouseManagerAnimatedData* curAnim; // 0x638E08 static unsigned char* curMouseBuf; // 0x638E0C static int lastMouseIndex; // 0x485250 static char* defaultNameMangler(char* a1) { return a1; } // 0x485254 static int defaultRateCallback() { return 1000; } // 0x48525C static int defaultTimeCallback() { return get_time(); } // NOTE: Inlined. // // 0x485264 static void setShape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans) { mouse_set_shape(buf, width, length, full, hotx, hoty, trans); } // 0x485288 void mousemgrSetNameMangler(MouseManagerNameMangler* func) { mouseNameMangler = func; } // NOTE: Unused. // // 0x485290 void mousemgrSetTimeCallback(MouseManagerRateProvider* rateFunc, MouseManagerTimeProvider* currentTimeFunc) { if (rateFunc != NULL) { rateCallback = rateFunc; } else { rateCallback = defaultRateCallback; } if (currentTimeFunc != NULL) { currentTimeCallback = currentTimeFunc; } else { currentTimeCallback = defaultTimeCallback; } } // 0x4852B8 static void freeCacheEntry(MouseManagerCacheEntry* entry) { switch (entry->type) { case MOUSE_MANAGER_MOUSE_TYPE_STATIC: if (entry->staticData != NULL) { if (entry->staticData->data != NULL) { myfree(entry->staticData->data, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 120 entry->staticData->data = NULL; } myfree(entry->staticData, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 123 entry->staticData = NULL; } break; case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED: if (entry->animatedData != NULL) { if (entry->animatedData->field_0 != NULL) { for (int index = 0; index < entry->animatedData->frameCount; index++) { myfree(entry->animatedData->field_0[index], __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 134 myfree(entry->animatedData->field_4[index], __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 135 } myfree(entry->animatedData->field_0, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 137 myfree(entry->animatedData->field_4, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 138 myfree(entry->animatedData->field_8, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 139 myfree(entry->animatedData->field_C, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 140 } myfree(entry->animatedData, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 143 entry->animatedData = NULL; } break; } entry->type = 0; entry->fileName[0] = '\0'; } // 0x4853F8 static int cacheInsert(void** data, int type, unsigned char* palette, const char* fileName) { int foundIndex = -1; int index; for (index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) { MouseManagerCacheEntry* cacheEntry = &(Cache[index]); if (cacheEntry->type == MOUSE_MANAGER_MOUSE_TYPE_NONE && foundIndex == -1) { foundIndex = index; } if (stricmp(fileName, cacheEntry->fileName) == 0) { freeCacheEntry(cacheEntry); foundIndex = index; break; } } if (foundIndex != -1) { index = foundIndex; } if (index == MOUSE_MGR_CACHE_CAPACITY) { int v2 = -1; int v1 = curref; for (int index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) { MouseManagerCacheEntry* cacheEntry = &(Cache[index]); if (v1 > cacheEntry->ref) { v1 = cacheEntry->ref; v2 = index; } } if (v2 == -1) { debug_printf("Mouse cache overflow!!!!\n"); exit(1); } index = v2; freeCacheEntry(&(Cache[index])); } MouseManagerCacheEntry* cacheEntry = &(Cache[index]); cacheEntry->type = type; memcpy(cacheEntry->palette, palette, sizeof(cacheEntry->palette)); cacheEntry->ref = curref++; strncpy(cacheEntry->fileName, fileName, sizeof(cacheEntry->fileName) - 1); cacheEntry->field_32C[0] = '\0'; cacheEntry->data = *data; return index; } // NOTE: Inlined. // // 0x4853D4 static void cacheFlush() { for (int index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) { freeCacheEntry(&(Cache[index])); } } // 0x48554C static MouseManagerCacheEntry* cacheFind(const char* fileName, unsigned char** palettePtr, int* a3, int* a4, int* widthPtr, int* heightPtr, int* typePtr) { for (int index = 0; index < MOUSE_MGR_CACHE_CAPACITY; index++) { MouseManagerCacheEntry* cacheEntry = &(Cache[index]); if (strnicmp(cacheEntry->fileName, fileName, 31) == 0 || strnicmp(cacheEntry->field_32C, fileName, 31) == 0) { *palettePtr = cacheEntry->palette; *typePtr = cacheEntry->type; lastMouseIndex = index; switch (cacheEntry->type) { case MOUSE_MANAGER_MOUSE_TYPE_STATIC: *a3 = cacheEntry->staticData->field_4; *a4 = cacheEntry->staticData->field_8; *widthPtr = cacheEntry->staticData->width; *heightPtr = cacheEntry->staticData->height; break; case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED: *widthPtr = cacheEntry->animatedData->width; *heightPtr = cacheEntry->animatedData->height; *a3 = cacheEntry->animatedData->field_8[cacheEntry->animatedData->field_26]; *a4 = cacheEntry->animatedData->field_C[cacheEntry->animatedData->field_26]; break; } return cacheEntry; } } return NULL; } // 0x48568C void initMousemgr() { mouse_set_sensitivity(1.0); } // 0x48569C void mousemgrClose() { setShape(NULL, 0, 0, 0, 0, 0, 0); if (curMouseBuf != NULL) { myfree(curMouseBuf, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 243 curMouseBuf = NULL; } // NOTE: Uninline. cacheFlush(); curPal = NULL; curAnim = 0; } // 0x485704 void mousemgrUpdate() { if (!animating) { return; } if (curAnim == NULL) { debug_printf("Animating == 1 but curAnim == 0\n"); } if (currentTimeCallback() >= curAnim->field_1C) { curAnim->field_1C = (int)(curAnim->field_18 / curAnim->frameCount * rateCallback() + currentTimeCallback()); if (curAnim->field_24 != curAnim->field_26) { int v1 = curAnim->field_26 + curAnim->field_20; if (v1 < 0) { v1 = curAnim->frameCount - 1; } else if (v1 >= curAnim->frameCount) { v1 = 0; } curAnim->field_26 = v1; memcpy(curAnim->field_0[curAnim->field_26], curAnim->field_4[curAnim->field_26], curAnim->width * curAnim->height); datafileConvertData(curAnim->field_0[curAnim->field_26], curPal, curAnim->width, curAnim->height); setShape(curAnim->field_0[v1], curAnim->width, curAnim->height, curAnim->width, curAnim->field_8[v1], curAnim->field_C[v1], 0); } } } // 0x485868 int mouseSetFrame(char* fileName, int a2) { char* mangledFileName = mouseNameMangler(fileName); unsigned char* palette; int temp; int type; MouseManagerCacheEntry* cacheEntry = cacheFind(fileName, &palette, &temp, &temp, &temp, &temp, &type); if (cacheEntry != NULL) { if (type == MOUSE_MANAGER_MOUSE_TYPE_ANIMATED) { cacheEntry->animatedData->field_24 = a2; if (cacheEntry->animatedData->field_24 >= cacheEntry->animatedData->field_26) { int v1 = cacheEntry->animatedData->field_24 - cacheEntry->animatedData->field_26; int v2 = cacheEntry->animatedData->frameCount + cacheEntry->animatedData->field_26 - cacheEntry->animatedData->field_24; if (v1 >= v2) { cacheEntry->animatedData->field_20 = -1; } else { cacheEntry->animatedData->field_20 = 1; } } else { int v1 = cacheEntry->animatedData->field_26 - cacheEntry->animatedData->field_24; int v2 = cacheEntry->animatedData->frameCount + cacheEntry->animatedData->field_24 - cacheEntry->animatedData->field_26; if (v1 < v2) { cacheEntry->animatedData->field_20 = -1; } else { cacheEntry->animatedData->field_20 = 1; } } if (!animating || curAnim != cacheEntry->animatedData) { memcpy(cacheEntry->animatedData->field_0[cacheEntry->animatedData->field_26], cacheEntry->animatedData->field_4[cacheEntry->animatedData->field_26], cacheEntry->animatedData->width * cacheEntry->animatedData->height); setShape(cacheEntry->animatedData->field_0[cacheEntry->animatedData->field_26], cacheEntry->animatedData->width, cacheEntry->animatedData->height, cacheEntry->animatedData->width, cacheEntry->animatedData->field_8[cacheEntry->animatedData->field_26], cacheEntry->animatedData->field_C[cacheEntry->animatedData->field_26], 0); animating = true; } curAnim = cacheEntry->animatedData; curPal = palette; curAnim->field_1C = currentTimeCallback(); return true; } mouseSetMousePointer(fileName); return true; } if (animating) { curPal = 0; animating = 0; curAnim = 0; } else { if (curMouseBuf != NULL) { myfree(curMouseBuf, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 337 curMouseBuf = NULL; } } File* stream = db_fopen(mangledFileName, "r"); if (stream == NULL) { debug_printf("mouseSetFrame: couldn't find %s\n", mangledFileName); return false; } char string[80]; db_fgets(string, sizeof(string), stream); if (strnicmp(string, "anim", 4) != 0) { db_fclose(stream); mouseSetMousePointer(fileName); return true; } // NOTE: Uninline. char* sep = strchr(string, ' '); if (sep == NULL) { // FIXME: Leaks stream. return false; } int v3; float v4; sscanf(sep + 1, "%d %f", &v3, &v4); MouseManagerAnimatedData* animatedData = (MouseManagerAnimatedData*)mymalloc(sizeof(*animatedData), __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 359 animatedData->field_0 = (unsigned char**)mymalloc(sizeof(*animatedData->field_0) * v3, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 360 animatedData->field_4 = (unsigned char**)mymalloc(sizeof(*animatedData->field_4) * v3, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 361 animatedData->field_8 = (int*)mymalloc(sizeof(*animatedData->field_8) * v3, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 362 animatedData->field_C = (int*)mymalloc(sizeof(*animatedData->field_8) * v3, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 363 animatedData->field_18 = v4; animatedData->field_1C = currentTimeCallback(); animatedData->field_26 = 0; animatedData->field_24 = a2; animatedData->frameCount = v3; if (animatedData->frameCount / 2 <= a2) { animatedData->field_20 = -1; } else { animatedData->field_20 = 1; } int width; int height; for (int index = 0; index < v3; index++) { string[0] = '\0'; db_fgets(string, sizeof(string), stream); if (string[0] == '\0') { debug_printf("Not enough frames in %s, got %d, needed %d", mangledFileName, index, v3); break; } // NOTE: Uninline. char* sep = strchr(string, ' '); if (sep == NULL) { debug_printf("Bad line %s in %s\n", string, fileName); // FIXME: Leaking stream. return false; } *sep = '\0'; int v5; int v6; sscanf(sep + 1, "%d %d", &v5, &v6); animatedData->field_4[index] = loadRawDataFile(mouseNameMangler(string), &width, &height); animatedData->field_0[index] = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 390 memcpy(animatedData->field_0[index], animatedData->field_4[index], width * height); datafileConvertData(animatedData->field_0[index], datafileGetPalette(), width, height); animatedData->field_8[index] = v5; animatedData->field_C[index] = v6; } db_fclose(stream); animatedData->width = width; animatedData->height = height; lastMouseIndex = cacheInsert(&animatedData, MOUSE_MANAGER_MOUSE_TYPE_ANIMATED, datafileGetPalette(), fileName); strncpy(Cache[lastMouseIndex].field_32C, fileName, 31); curAnim = animatedData; curPal = Cache[lastMouseIndex].palette; animating = true; setShape(animatedData->field_0[0], animatedData->width, animatedData->height, animatedData->width, animatedData->field_8[0], animatedData->field_C[0], 0); return true; } // 0x485E58 bool mouseSetMouseShape(char* fileName, int a2, int a3) { unsigned char* palette; int temp; int width; int height; int type; MouseManagerCacheEntry* cacheEntry = cacheFind(fileName, &palette, &temp, &temp, &width, &height, &type); char* mangledFileName = mouseNameMangler(fileName); if (cacheEntry == NULL) { MouseManagerStaticData* staticData; staticData = (MouseManagerStaticData*)mymalloc(sizeof(*staticData), __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 430 staticData->data = loadRawDataFile(mangledFileName, &width, &height); staticData->field_4 = a2; staticData->field_8 = a3; staticData->width = width; staticData->height = height; lastMouseIndex = cacheInsert(&staticData, MOUSE_MANAGER_MOUSE_TYPE_STATIC, datafileGetPalette(), fileName); // NOTE: Original code is slightly different. It obtains address of // `staticData` and sets it's it into `cacheEntry`, which is a bit // awkward. Maybe there is more level on indirection was used. Any way // in order to make code path below unaltered take entire cache entry. cacheEntry = &(Cache[lastMouseIndex]); type = MOUSE_MANAGER_MOUSE_TYPE_STATIC; palette = Cache[lastMouseIndex].palette; } switch (type) { case MOUSE_MANAGER_MOUSE_TYPE_STATIC: if (curMouseBuf != NULL) { myfree(curMouseBuf, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 446 } curMouseBuf = mymalloc(width * height, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 448 memcpy(curMouseBuf, cacheEntry->staticData->data, width * height); datafileConvertData(curMouseBuf, palette, width, height); setShape(curMouseBuf, width, height, width, a2, a3, 0); animating = false; break; case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED: curAnim = cacheEntry->animatedData; animating = true; curPal = palette; break; } return true; } // 0x486010 bool mouseSetMousePointer(char* fileName) { unsigned char* palette; int v1; int v2; int width; int height; int type; MouseManagerCacheEntry* cacheEntry = cacheFind(fileName, &palette, &v1, &v2, &width, &height, &type); if (cacheEntry != NULL) { if (curMouseBuf != NULL) { myfree(curMouseBuf, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 482 curMouseBuf = NULL; } curPal = NULL; animating = false; curAnim = 0; switch (type) { case MOUSE_MANAGER_MOUSE_TYPE_STATIC: curMouseBuf = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 492 memcpy(curMouseBuf, cacheEntry->staticData->data, width * height); datafileConvertData(curMouseBuf, palette, width, height); setShape(curMouseBuf, width, height, width, v1, v2, 0); animating = false; break; case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED: curAnim = cacheEntry->animatedData; curPal = palette; curAnim->field_26 = 0; curAnim->field_24 = 0; setShape(curAnim->field_0[0], curAnim->width, curAnim->height, curAnim->width, curAnim->field_8[0], curAnim->field_C[0], 0); animating = true; break; } return true; } char* dot = strrchr(fileName, '.'); if (dot != NULL && stricmp(dot + 1, "mou") == 0) { return mouseSetMouseShape(fileName, 0, 0); } char* mangledFileName = mouseNameMangler(fileName); File* stream = db_fopen(mangledFileName, "r"); if (stream == NULL) { debug_printf("Can't find %s\n", mangledFileName); return false; } char string[80]; string[0] = '\0'; db_fgets(string, sizeof(string) - 1, stream); if (string[0] == '\0') { return false; } bool rc; if (strnicmp(string, "anim", 4) == 0) { db_fclose(stream); rc = mouseSetFrame(fileName, 0); } else { // NOTE: Uninline. char* sep = strchr(string, ' '); if (sep != NULL) { return 0; } *sep = '\0'; int v3; int v4; sscanf(sep + 1, "%d %d", &v3, &v4); db_fclose(stream); rc = mouseSetMouseShape(string, v3, v4); } strncpy(Cache[lastMouseIndex].field_32C, fileName, 31); return rc; } // 0x4862AC void mousemgrResetMouse() { MouseManagerCacheEntry* entry = &(Cache[lastMouseIndex]); int imageWidth; int imageHeight; switch (entry->type) { case MOUSE_MANAGER_MOUSE_TYPE_STATIC: imageWidth = entry->staticData->width; imageHeight = entry->staticData->height; break; case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED: imageWidth = entry->animatedData->width; imageHeight = entry->animatedData->height; break; } switch (entry->type) { case MOUSE_MANAGER_MOUSE_TYPE_STATIC: if (curMouseBuf != NULL) { if (curMouseBuf != NULL) { myfree(curMouseBuf, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 572 } curMouseBuf = (unsigned char*)mymalloc(imageWidth * imageHeight, __FILE__, __LINE__); // "..\\int\\MOUSEMGR.C", 574 memcpy(curMouseBuf, entry->staticData->data, imageWidth * imageHeight); datafileConvertData(curMouseBuf, entry->palette, imageWidth, imageHeight); setShape(curMouseBuf, imageWidth, imageHeight, imageWidth, entry->staticData->field_4, entry->staticData->field_8, 0); } else { debug_printf("Hm, current mouse type is M_STATIC, but no current mouse pointer\n"); } break; case MOUSE_MANAGER_MOUSE_TYPE_ANIMATED: if (curAnim != NULL) { for (int index = 0; index < curAnim->frameCount; index++) { memcpy(curAnim->field_0[index], curAnim->field_4[index], imageWidth * imageHeight); datafileConvertData(curAnim->field_0[index], entry->palette, imageWidth, imageHeight); } setShape(curAnim->field_0[curAnim->field_26], imageWidth, imageHeight, imageWidth, curAnim->field_8[curAnim->field_26], curAnim->field_C[curAnim->field_26], 0); } else { debug_printf("Hm, current mouse type is M_ANIMATED, but no current mouse pointer\n"); } } } // 0x4865C4 void mouseHide() { mouse_hide(); } // 0x4865CC void mouseShow() { mouse_show(); } ================================================ FILE: src/int/mousemgr.h ================================================ #ifndef FALLOUT_INT_MOUSEMGR_H_ #define FALLOUT_INT_MOUSEMGR_H_ #include typedef char*(MouseManagerNameMangler)(char* fileName); typedef int(MouseManagerRateProvider)(); typedef int(MouseManagerTimeProvider)(); void mousemgrSetNameMangler(MouseManagerNameMangler* func); void mousemgrSetTimeCallback(MouseManagerRateProvider* rateFunc, MouseManagerTimeProvider* currentTimeFunc); void initMousemgr(); void mousemgrClose(); void mousemgrUpdate(); int mouseSetFrame(char* fileName, int a2); bool mouseSetMouseShape(char* fileName, int a2, int a3); bool mouseSetMousePointer(char* fileName); void mousemgrResetMouse(); void mouseHide(); void mouseShow(); #endif /* FALLOUT_INT_MOUSEMGR_H_ */ ================================================ FILE: src/int/movie.c ================================================ #include "int/movie.h" #include #include "int/window.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "plib/db/db.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "game/gconfig.h" #include "int/memdbg.h" #include "game/moviefx.h" #include "movie_lib.h" #include "int/sound.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" #include "plib/gnw/winmain.h" typedef void(MovieCallback)(); typedef int(MovieBlitFunc)(int win, unsigned char* data, int width, int height, int pitch); typedef struct MovieSubtitleListNode { int num; char* text; struct MovieSubtitleListNode* next; } MovieSubtitleListNode; static void* movieMalloc(size_t size); static void movieFree(void* ptr); static bool movieRead(int fileHandle, void* buf, int count); static void movie_MVE_ShowFrame(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); static void movieShowFrame(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); static int movieScaleSubRect(int win, unsigned char* data, int width, int height, int pitch); static int movieScaleWindowAlpha(int win, unsigned char* data, int width, int height, int pitch); static int movieScaleSubRectAlpha(int win, unsigned char* data, int width, int height, int pitch); static int blitAlpha(int win, unsigned char* data, int width, int height, int pitch); static int movieScaleWindow(int win, unsigned char* data, int width, int height, int pitch); static int blitNormal(int win, unsigned char* data, int width, int height, int pitch); static void movieSetPalette(unsigned char* palette, int start, int end); static int noop(); static void cleanupMovie(int a1); static void cleanupLast(); static File* openFile(char* filePath); static void openSubtitle(char* filePath); static void doSubtitle(); static int movieStart(int win, char* filePath, int (*a3)()); static bool localMovieCallback(); static int stepMovie(); // 0x5195B8 static int GNWWin = -1; // 0x5195BC static int subtitleFont = -1; // 0x5195C0 static MovieBlitFunc* showFrameFuncs[2][2][2] = { { { blitNormal, blitNormal, }, { movieScaleWindow, movieScaleSubRect, }, }, { { blitAlpha, blitAlpha, }, { movieScaleSubRectAlpha, movieScaleWindowAlpha, }, }, }; // 0x5195E0 static MoviePaletteFunc* paletteFunc = setSystemPaletteEntries; // 0x5195E4 static int subtitleR = 31; // 0x5195E8 static int subtitleG = 31; // 0x5195EC static int subtitleB = 31; // 0x638E10 static Rect winRect; // 0x638E20 static Rect movieRect; // 0x638E30 static MovieCallback* movieCallback; // 0x638E34 static MovieEndFunc* endMovieFunc; // 0x638E38 static MovieUpdateCallbackProc* updateCallbackFunc; // 0x638E3C static MovieFailedOpenFunc* failedOpenFunc; // 0x638E40 static MovieSubtitleFunc* subtitleFilenameFunc; // 0x638E44 static MovieStartFunc* startMovieFunc; // 0x638E48 static int subtitleW; // 0x638E4C static int lastMovieBH; // 0x638E50 static int lastMovieBW; // 0x638E54 static int lastMovieSX; // 0x638E58 static int lastMovieSY; // 0x638E5C static int movieScaleFlag; // 0x638E60 static MoviePreDrawFunc* moviePreDrawFunc; // 0x638E64 static int lastMovieH; // 0x638E68 static int lastMovieW; // 0x638E6C static int lastMovieX; // 0x638E70 static int lastMovieY; // 0x638E74 static MovieSubtitleListNode* subtitleList; // 0x638E78 static unsigned int movieFlags; // 0x638E7C static int movieAlphaFlag; // 0x638E80 static bool movieSubRectFlag; // 0x638E84 static int movieH; // 0x638E88 static int movieOffset; // 0x638E8C static MovieCaptureFrameProc* movieCaptureFrameFunc; // 0x638E90 static unsigned char* lastMovieBuffer; // 0x638E94 static int movieW; // 0x638E98 static MovieFrameGrabProc* movieFrameGrabFunc; // 0x638E9C static LPDIRECTDRAWSURFACE MVE_lastBuffer; // 0x638EA0 static int subtitleH; // 0x638EA4 static int running; // 0x638EA8 static File* handle; // 0x638EAC static unsigned char* alphaWindowBuf; // 0x638EB0 static int movieX; // 0x638EB4 static int movieY; // 0x638EB8 static bool soundEnabled; // 0x638EBC static File* alphaHandle; // 0x638EC0 static unsigned char* alphaBuf; // NOTE: Unused. // // 0x4865E0 void movieSetPreDrawFunc(MoviePreDrawFunc* func) { moviePreDrawFunc = func; } // NOTE: Unused. // // 0x4865E8 void movieSetFailedOpenFunc(MovieFailedOpenFunc* func) { failedOpenFunc = func; } // NOTE: Unused. // // 0x4865F0 void movieSetFunc(MovieStartFunc* startFunc, MovieEndFunc* endFunc) { startMovieFunc = startFunc; endMovieFunc = endFunc; } // 0x4865FC static void* movieMalloc(size_t size) { return mymalloc(size, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 209 } // 0x486614 static void movieFree(void* ptr) { myfree(ptr, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 213 } // 0x48662C static bool movieRead(int fileHandle, void* buf, int count) { return db_fread(buf, 1, count, (File*)fileHandle) == count; } // 0x486654 static void movie_MVE_ShowFrame(LPDIRECTDRAWSURFACE surface, int srcWidth, int srcHeight, int srcX, int srcY, int destWidth, int destHeight, int a8, int a9) { int v14; int v15; DDSURFACEDESC ddsd; memset(&ddsd, 0, sizeof(DDSURFACEDESC)); ddsd.dwSize = sizeof(DDSURFACEDESC); RECT srcRect; srcRect.left = srcX; srcRect.top = srcY; srcRect.right = srcWidth + srcX; srcRect.bottom = srcHeight + srcY; v14 = winRect.lrx - winRect.ulx; v15 = winRect.lrx - winRect.ulx + 1; RECT destRect; if (movieScaleFlag) { if ((movieFlags & MOVIE_EXTENDED_FLAG_0x08) != 0) { destRect.top = (winRect.lry - winRect.uly + 1 - destHeight) / 2; destRect.left = (v15 - 4 * srcWidth / 3) / 2; } else { destRect.top = movieY + winRect.uly; destRect.left = winRect.ulx + movieX; } destRect.right = 4 * srcWidth / 3 + destRect.left; destRect.bottom = destHeight + destRect.top; } else { if ((movieFlags & MOVIE_EXTENDED_FLAG_0x08) != 0) { destRect.top = (winRect.lry - winRect.uly + 1 - destHeight) / 2; destRect.left = (v15 - destWidth) / 2; } else { destRect.top = movieY + winRect.uly; destRect.left = winRect.ulx + movieX; } destRect.right = destWidth + destRect.left; destRect.bottom = destHeight + destRect.top; } lastMovieSX = srcX; lastMovieSY = srcY; lastMovieX = destRect.left; lastMovieY = destRect.top; lastMovieBH = srcHeight; lastMovieW = destRect.right - destRect.left; MVE_lastBuffer = surface; lastMovieBW = srcWidth; lastMovieH = destRect.bottom - destRect.top; HRESULT hr; do { if (movieCaptureFrameFunc != NULL) { if (IDirectDrawSurface_Lock(surface, NULL, &ddsd, 1, NULL) == DD_OK) { unsigned char* data = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * srcY + srcX; movieCaptureFrameFunc(data, srcWidth, srcHeight, ddsd.lPitch, destRect.left, destRect.top, destRect.right - destRect.left, destRect.bottom - destRect.top); IDirectDrawSurface_Unlock(surface, ddsd.lpSurface); } } hr = IDirectDrawSurface_Blt(GNW95_DDPrimarySurface, &destRect, surface, &srcRect, 0, NULL); } while (hr != DD_OK && hr != DDERR_SURFACELOST && hr == DDERR_WASSTILLDRAWING); } // 0x486900 static void movieShowFrame(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { if (GNWWin == -1) { return; } lastMovieBW = a2; MVE_lastBuffer = a1; lastMovieBH = a2; lastMovieW = a6; lastMovieH = a7; lastMovieX = a4; lastMovieY = a5; lastMovieSX = a4; lastMovieSY = a5; DDSURFACEDESC ddsd; ddsd.dwSize = sizeof(DDSURFACEDESC); if (IDirectDrawSurface_Lock(a1, NULL, &ddsd, 1, NULL) != DD_OK) { return; } unsigned char* data = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * a5 + a4; if (movieCaptureFrameFunc != NULL) { // FIXME: Looks wrong as it ignores lPitch (as seen in movie_MVE_ShowFrame). movieCaptureFrameFunc(data, a2, a3, a2, movieRect.ulx, movieRect.uly, a6, a7); } if (movieFrameGrabFunc != NULL) { movieFrameGrabFunc(data, a2, a3, ddsd.lPitch); } else { MovieBlitFunc* func = showFrameFuncs[movieAlphaFlag][movieScaleFlag][movieSubRectFlag]; if (func(GNWWin, data, a2, a3, ddsd.lPitch) != 0) { if (moviePreDrawFunc != NULL) { moviePreDrawFunc(GNWWin, &movieRect); } win_draw_rect(GNWWin, &movieRect); } } IDirectDrawSurface_Unlock(a1, ddsd.lpSurface); } // NOTE: Unused. // // 0x486A98 void movieSetFrameGrabFunc(MovieFrameGrabProc* func) { movieFrameGrabFunc = func; } // NOTE: Unused. // // 0x486AA0 void movieSetCaptureFrameFunc(MovieCaptureFrameProc* func) { movieCaptureFrameFunc = func; } // 0x486B68 static int movieScaleSubRect(int win, unsigned char* data, int width, int height, int pitch) { int windowWidth = win_width(win); unsigned char* windowBuffer = win_get_buf(win) + windowWidth * movieY + movieX; if (width * 4 / 3 > movieW) { movieFlags |= 0x01; return 0; } int v1 = width / 3; for (int y = 0; y < height; y++) { int x; for (x = 0; x < v1; x++) { unsigned int value = data[0]; value |= data[1] << 8; value |= data[2] << 16; value |= data[2] << 24; *(unsigned int*)windowBuffer = value; windowBuffer += 4; data += 3; } for (x = x * 3; x < width; x++) { *windowBuffer++ = *data++; } data += pitch - width; windowBuffer += windowWidth - movieW; } return 1; } // 0x486C74 static int movieScaleWindowAlpha(int win, unsigned char* data, int width, int height, int pitch) { movieFlags |= 1; return 0; } // NOTE: Uncollapsed 0x486C74. static int movieScaleSubRectAlpha(int win, unsigned char* data, int width, int height, int pitch) { movieFlags |= 1; return 0; } // 0x486C80 static int blitAlpha(int win, unsigned char* data, int width, int height, int pitch) { int windowWidth = win_width(win); unsigned char* windowBuffer = win_get_buf(win); alphaBltBuf(data, width, height, pitch, alphaWindowBuf, alphaBuf, windowBuffer + windowWidth * movieY + movieX, windowWidth); return 1; } // 0x486CD4 static int movieScaleWindow(int win, unsigned char* data, int width, int height, int pitch) { int windowWidth = win_width(win); if (width != 3 * windowWidth / 4) { movieFlags |= 1; return 0; } unsigned char* windowBuffer = win_get_buf(win); for (int y = 0; y < height; y++) { int scaledWidth = width / 3; for (int x = 0; x < scaledWidth; x++) { unsigned int value = data[0]; value |= data[1] << 8; value |= data[2] << 16; value |= data[3] << 24; *(unsigned int*)windowBuffer = value; windowBuffer += 4; data += 3; } data += pitch - width; } return 1; } // 0x486D84 static int blitNormal(int win, unsigned char* data, int width, int height, int pitch) { int windowWidth = win_width(win); unsigned char* windowBuffer = win_get_buf(win); drawScaled(windowBuffer + windowWidth * movieY + movieX, movieW, movieH, windowWidth, data, width, height, pitch); return 1; } // 0x486DDC static void movieSetPalette(unsigned char* palette, int start, int end) { if (end != 0) { paletteFunc(palette + start * 3, start, end + start - 1); } } // 0x486E08 static int noop() { return 0; } // initMovie // 0x486E0C void initMovie() { movieLibSetMemoryProcs(movieMalloc, movieFree); movieLibSetDirectSound(soundDSObject); soundEnabled = (soundDSObject != NULL); movieLibSetDirectDraw(GNW95_DDObject); movieLibSetPaletteEntriesProc(movieSetPalette); _MVE_sfSVGA(640, 480, 480, 0, 0, 0, 0, 0, 0); movieLibSetReadProc(movieRead); } // 0x486E98 static void cleanupMovie(int a1) { if (!running) { return; } if (endMovieFunc != NULL) { endMovieFunc(GNWWin, movieX, movieY, movieW, movieH); } int frame; int dropped; _MVE_rmFrameCounts(&frame, &dropped); debug_printf("Frames %d, dropped %d\n", frame, dropped); if (lastMovieBuffer != NULL) { myfree(lastMovieBuffer, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 787 lastMovieBuffer = NULL; } if (MVE_lastBuffer != NULL) { DDSURFACEDESC ddsd; ddsd.dwSize = sizeof(DDSURFACEDESC); if (IDirectDrawSurface_Lock(MVE_lastBuffer, 0, &ddsd, 1, NULL) == DD_OK) { lastMovieBuffer = (unsigned char*)mymalloc(lastMovieBH * lastMovieBW, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 802 buf_to_buf((unsigned char*)ddsd.lpSurface + ddsd.lPitch * lastMovieSX + lastMovieSY, lastMovieBW, lastMovieBH, ddsd.lPitch, lastMovieBuffer, lastMovieBW); IDirectDrawSurface_Unlock(MVE_lastBuffer, ddsd.lpSurface); } else { debug_printf("Couldn't lock movie surface\n"); } MVE_lastBuffer = NULL; } if (a1) { _MVE_rmEndMovie(); } _MVE_ReleaseMem(); db_fclose(handle); if (alphaWindowBuf != NULL) { buf_to_buf(alphaWindowBuf, movieW, movieH, movieW, win_get_buf(GNWWin) + movieY * win_width(GNWWin) + movieX, win_width(GNWWin)); win_draw_rect(GNWWin, &movieRect); } if (alphaHandle != NULL) { db_fclose(alphaHandle); alphaHandle = NULL; } if (alphaBuf != NULL) { myfree(alphaBuf, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 840 alphaBuf = NULL; } if (alphaWindowBuf != NULL) { myfree(alphaWindowBuf, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 845 alphaWindowBuf = NULL; } while (subtitleList != NULL) { MovieSubtitleListNode* next = subtitleList->next; myfree(subtitleList->text, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 851 myfree(subtitleList, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 852 subtitleList = next; } running = 0; movieSubRectFlag = 0; movieScaleFlag = 0; movieAlphaFlag = 0; movieFlags = 0; GNWWin = -1; } // 0x48711C void movieClose() { cleanupMovie(1); if (lastMovieBuffer) { myfree(lastMovieBuffer, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 869 lastMovieBuffer = NULL; } } // 0x487150 void movieStop() { if (running) { movieFlags |= MOVIE_EXTENDED_FLAG_0x02; } } // 0x487164 int movieSetFlags(int flags) { if ((flags & MOVIE_FLAG_0x04) != 0) { movieFlags |= MOVIE_EXTENDED_FLAG_0x04 | MOVIE_EXTENDED_FLAG_0x08; } else { movieFlags &= ~MOVIE_EXTENDED_FLAG_0x08; if ((flags & MOVIE_FLAG_0x02) != 0) { movieFlags |= MOVIE_EXTENDED_FLAG_0x04; } else { movieFlags &= ~MOVIE_EXTENDED_FLAG_0x04; } } if ((flags & MOVIE_FLAG_0x01) != 0) { movieScaleFlag = 1; if ((movieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) { _sub_4F4BB(3); } } else { movieScaleFlag = 0; if ((movieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) { _sub_4F4BB(4); } else { movieFlags &= ~MOVIE_EXTENDED_FLAG_0x08; } } if ((flags & MOVIE_FLAG_0x08) != 0) { movieFlags |= MOVIE_EXTENDED_FLAG_0x10; } else { movieFlags &= ~MOVIE_EXTENDED_FLAG_0x10; } return 0; } // NOTE: Unused. // // 0x48720C void movieSetSubtitleFont(int font) { subtitleFont = font; } // NOTE: Unused. // // 0x487214 void movieSetSubtitleColor(float r, float g, float b) { subtitleR = (int)(r * 31.0f); subtitleG = (int)(g * 31.0f); subtitleB = (int)(b * 31.0f); } // 0x48725C void movieSetPaletteFunc(MoviePaletteFunc* func) { paletteFunc = func != NULL ? func : setSystemPaletteEntries; } // 0x487274 void movieSetCallback(MovieUpdateCallbackProc* func) { updateCallbackFunc = func; } // 0x4872E8 static void cleanupLast() { if (lastMovieBuffer != NULL) { myfree(lastMovieBuffer, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 981 lastMovieBuffer = NULL; } MVE_lastBuffer = NULL; } // 0x48731C static File* openFile(char* filePath) { handle = db_fopen(filePath, "rb"); if (handle == NULL) { if (failedOpenFunc == NULL) { debug_printf("Couldn't find movie file %s\n", filePath); return 0; } while (handle == NULL && failedOpenFunc(filePath) != 0) { handle = db_fopen(filePath, "rb"); } } return handle; } // 0x487380 static void openSubtitle(char* filePath) { subtitleW = windowGetXres(); subtitleH = text_height() + 4; if (subtitleFilenameFunc != NULL) { filePath = subtitleFilenameFunc(filePath); } char path[MAX_PATH]; strcpy(path, filePath); debug_printf("Opening subtitle file %s\n", path); File* stream = db_fopen(path, "r"); if (stream == NULL) { debug_printf("Couldn't open subtitle file %s\n", path); movieFlags &= ~MOVIE_EXTENDED_FLAG_0x10; return; } MovieSubtitleListNode* prev = NULL; int subtitleCount = 0; while (!db_feof(stream)) { char string[260]; string[0] = '\0'; db_fgets(string, 259, stream); if (*string == '\0') { break; } MovieSubtitleListNode* subtitle = (MovieSubtitleListNode*)mymalloc(sizeof(*subtitle), __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1050 subtitle->next = NULL; subtitleCount++; char* pch; pch = strchr(string, '\n'); if (pch != NULL) { *pch = '\0'; } pch = strchr(string, '\r'); if (pch != NULL) { *pch = '\0'; } pch = strchr(string, ':'); if (pch != NULL) { *pch = '\0'; subtitle->num = atoi(string); subtitle->text = mystrdup(pch + 1, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1058 if (prev != NULL) { prev->next = subtitle; } else { subtitleList = subtitle; } prev = subtitle; } else { debug_printf("subtitle: couldn't parse %s\n", string); } } db_fclose(stream); debug_printf("Read %d subtitles\n", subtitleCount); } // 0x48755C static void doSubtitle() { if (subtitleList == NULL) { return; } if ((movieFlags & MOVIE_EXTENDED_FLAG_0x10) == 0) { return; } int v1 = text_height(); int v2 = (480 - lastMovieH - lastMovieY - v1) / 2 + lastMovieH + lastMovieY; if (subtitleH + v2 > windowGetYres()) { subtitleH = windowGetYres() - v2; } int frame; int dropped; _MVE_rmFrameCounts(&frame, &dropped); while (subtitleList != NULL) { if (frame < subtitleList->num) { break; } MovieSubtitleListNode* next = subtitleList->next; win_fill(GNWWin, 0, v2, subtitleW, subtitleH, 0); int oldFont; if (subtitleFont != -1) { oldFont = text_curr(); text_font(subtitleFont); } int colorIndex = (subtitleR << 10) | (subtitleG << 5) | subtitleB; windowWrapLine(GNWWin, subtitleList->text, subtitleW, subtitleH, 0, v2, colorTable[colorIndex] | 0x2000000, TEXT_ALIGNMENT_CENTER); Rect rect; rect.lrx = subtitleW; rect.uly = v2; rect.lry = v2 + subtitleH; rect.ulx = 0; win_draw_rect(GNWWin, &rect); myfree(subtitleList->text, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1108 myfree(subtitleList, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1109 subtitleList = next; if (subtitleFont != -1) { text_font(oldFont); } } } // 0x487710 static int movieStart(int win, char* filePath, int (*a3)()) { int v15; int v16; int v17; if (running) { return 1; } cleanupLast(); handle = openFile(filePath); if (handle == NULL) { return 1; } GNWWin = win; running = 1; movieFlags &= ~MOVIE_EXTENDED_FLAG_0x01; if ((movieFlags & MOVIE_EXTENDED_FLAG_0x10) != 0) { openSubtitle(filePath); } if ((movieFlags & MOVIE_EXTENDED_FLAG_0x04) != 0) { debug_printf("Direct "); win_get_rect(GNWWin, &winRect); debug_printf("Playing at (%d, %d) ", movieX + winRect.ulx, movieY + winRect.uly); _MVE_rmCallbacks(a3); _MVE_sfCallbacks(movie_MVE_ShowFrame); v17 = 0; v16 = movieY + winRect.uly; v15 = movieX + winRect.ulx; } else { debug_printf("Buffered "); _MVE_rmCallbacks(a3); _MVE_sfCallbacks(movieShowFrame); v17 = 0; v16 = 0; v15 = 0; } _MVE_rmPrepMovie((int)handle, v15, v16, v17); if (movieScaleFlag) { debug_printf("scaled\n"); } else { debug_printf("not scaled\n"); } if (startMovieFunc != NULL) { startMovieFunc(GNWWin); } if (alphaHandle != NULL) { unsigned long size; db_freadLong(alphaHandle, &size); short tmp; db_freadShort(alphaHandle, &tmp); db_freadShort(alphaHandle, &tmp); alphaBuf = (unsigned char*)mymalloc(size, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1178 alphaWindowBuf = (unsigned char*)mymalloc(movieH * movieW, __FILE__, __LINE__); // "..\\int\\MOVIE.C", 1179 unsigned char* windowBuffer = win_get_buf(GNWWin); buf_to_buf(windowBuffer + win_width(GNWWin) * movieY + movieX, movieW, movieH, win_width(GNWWin), alphaWindowBuf, movieW); } movieRect.ulx = movieX; movieRect.uly = movieY; movieRect.lrx = movieW + movieX; movieRect.lry = movieH + movieY; return 0; } // 0x487964 static bool localMovieCallback() { doSubtitle(); if (movieCallback != NULL) { movieCallback(); } return get_input() != -1; } // 0x487AC8 int movieRun(int win, char* filePath) { if (running) { return 1; } movieX = 0; movieY = 0; movieOffset = 0; movieW = win_width(win); movieH = win_height(win); movieSubRectFlag = 0; return movieStart(win, filePath, noop); } // 0x487B1C int movieRunRect(int win, char* filePath, int a3, int a4, int a5, int a6) { if (running) { return 1; } movieX = a3; movieY = a4; movieOffset = a3 + a4 * win_width(win); movieW = a5; movieH = a6; movieSubRectFlag = 1; return movieStart(win, filePath, noop); } // 0x487B7C static int stepMovie() { if (alphaHandle != NULL) { unsigned long size; db_freadLong(alphaHandle, &size); db_fread(alphaBuf, 1, size, alphaHandle); } int v1 = _MVE_rmStepMovie(); if (v1 != -1) { doSubtitle(); } return v1; } // 0x487BC8 void movieSetSubtitleFunc(MovieSubtitleFunc* func) { subtitleFilenameFunc = func; } // 0x487BD0 void movieSetVolume(int volume) { if (soundEnabled) { int normalizedVolume = soundVolumeHMItoDirectSound(volume); movieLibSetVolume(normalizedVolume); } } // 0x487BEC void movieUpdate() { if (!running) { return; } if ((movieFlags & MOVIE_EXTENDED_FLAG_0x02) != 0) { debug_printf("Movie aborted\n"); cleanupMovie(1); return; } if ((movieFlags & MOVIE_EXTENDED_FLAG_0x01) != 0) { debug_printf("Movie error\n"); cleanupMovie(1); return; } if (stepMovie() == -1) { cleanupMovie(1); return; } if (updateCallbackFunc != NULL) { int frame; int dropped; _MVE_rmFrameCounts(&frame, &dropped); updateCallbackFunc(frame); } } // 0x487C88 int moviePlaying() { return running; } ================================================ FILE: src/int/movie.h ================================================ #ifndef FALLOUT_INT_MOVIE_H_ #define FALLOUT_INT_MOVIE_H_ #include "plib/gnw/rect.h" typedef enum MovieFlags { MOVIE_FLAG_0x01 = 0x01, MOVIE_FLAG_0x02 = 0x02, MOVIE_FLAG_0x04 = 0x04, MOVIE_FLAG_0x08 = 0x08, } MovieFlags; typedef enum MovieExtendedFlags { MOVIE_EXTENDED_FLAG_0x01 = 0x01, MOVIE_EXTENDED_FLAG_0x02 = 0x02, MOVIE_EXTENDED_FLAG_0x04 = 0x04, MOVIE_EXTENDED_FLAG_0x08 = 0x08, MOVIE_EXTENDED_FLAG_0x10 = 0x10, } MovieExtendedFlags; typedef char*(MovieSubtitleFunc)(char* movieFilePath); typedef void(MoviePaletteFunc)(unsigned char* palette, int start, int end); typedef void(MovieUpdateCallbackProc)(int frame); typedef void(MovieFrameGrabProc)(unsigned char* data, int width, int height, int pitch); typedef void(MovieCaptureFrameProc)(unsigned char* data, int width, int height, int pitch, int movieX, int movieY, int movieWidth, int movieHeight); typedef void(MoviePreDrawFunc)(int win, Rect* rect); typedef void(MovieStartFunc)(int win); typedef void(MovieEndFunc)(int win, int x, int y, int width, int height); typedef int(MovieFailedOpenFunc)(char* path); void movieSetPreDrawFunc(MoviePreDrawFunc* func); void movieSetFailedOpenFunc(MovieFailedOpenFunc* func); void movieSetFunc(MovieStartFunc* startFunc, MovieEndFunc* endFunc); void movieSetFrameGrabFunc(MovieFrameGrabProc* func); void movieSetCaptureFrameFunc(MovieCaptureFrameProc* func); void initMovie(); void movieClose(); void movieStop(); int movieSetFlags(int a1); void movieSetSubtitleFont(int font); void movieSetSubtitleColor(float r, float g, float b); void movieSetPaletteFunc(MoviePaletteFunc* func); void movieSetCallback(MovieUpdateCallbackProc* func); int movieRun(int win, char* filePath); int movieRunRect(int win, char* filePath, int a3, int a4, int a5, int a6); void movieSetSubtitleFunc(MovieSubtitleFunc* proc); void movieSetVolume(int volume); void movieUpdate(); int moviePlaying(); #endif /* FALLOUT_INT_MOVIE_H_ */ ================================================ FILE: src/int/nevs.c ================================================ #include "int/nevs.h" #include #include #include "plib/gnw/debug.h" #include "int/intlib.h" #include "int/memdbg.h" #define NEVS_COUNT 40 typedef struct Nevs { bool used; char name[32]; Program* program; int proc; int type; int hits; bool busy; NevsCallback* callback; } Nevs; static_assert(sizeof(Nevs) == 60, "wrong size"); static Nevs* nevs_alloc(); static void nevs_free(Nevs* nevs); static void nevs_removeprogramreferences(Program* program); static Nevs* nevs_find(const char* name); // 0x6391C8 static Nevs* nevs; // 0x6391CC static int anyhits; // 0x488340 static Nevs* nevs_alloc() { int index; Nevs* entry; if (nevs == NULL) { debug_printf("nevs_alloc(): nevs_initonce() not called!"); exit(99); } for (index = 0; index < NEVS_COUNT; index++) { entry = &(nevs[index]); if (!entry->used) { // NOTE: Uninline. nevs_free(entry); return entry; } } return NULL; } // NOTE: Inlined. // // 0x488394 static void nevs_free(Nevs* entry) { entry->used = false; memset(entry, 0, sizeof(*entry)); } // 0x4883AC void nevs_close() { if (nevs != NULL) { myfree(nevs, __FILE__, __LINE__); // "..\\int\\NEVS.C", 97 nevs = NULL; } } // 0x4883D4 static void nevs_removeprogramreferences(Program* program) { int index; Nevs* entry; if (nevs != NULL) { for (index = 0; index < NEVS_COUNT; index++) { entry = &(nevs[index]); if (entry->used && entry->program == program) { // NOTE: Uninline. nevs_free(entry); } } } } // 0x488418 void nevs_initonce() { interpretRegisterProgramDeleteCallback(nevs_removeprogramreferences); if (nevs == NULL) { nevs = (Nevs*)mycalloc(sizeof(Nevs), NEVS_COUNT, __FILE__, __LINE__); // "..\\int\\NEVS.C", 131 if (nevs == NULL) { debug_printf("nevs_initonce(): out of memory"); exit(99); } } } // 0x48846C static Nevs* nevs_find(const char* name) { int index; Nevs* entry; if (nevs == NULL) { debug_printf("nevs_find(): nevs_initonce() not called!"); exit(99); } for (index = 0; index < NEVS_COUNT; index++) { entry = &(nevs[index]); if (entry->used && stricmp(entry->name, name) == 0) { return entry; } } return NULL; } // 0x4884C8 int nevs_addevent(const char* name, Program* program, int proc, int type) { Nevs* entry; entry = nevs_find(name); if (entry == NULL) { entry = nevs_alloc(); } if (entry == NULL) { return 1; } entry->used = true; strcpy(entry->name, name); entry->program = program; entry->proc = proc; entry->type = type; entry->callback = NULL; return 0; } // NOTE: Unused. // // 0x488528 int nevs_addCevent(const char* name, NevsCallback* callback, int type) { Nevs* entry; debug_printf("nevs_addCevent( '%s', %p);\n", name, callback); entry = nevs_find(name); if (entry == NULL) { entry = nevs_alloc(); } if (entry == NULL) { return 1; } entry->used = true; strcpy(entry->name, name); entry->program = NULL; entry->proc = 0; entry->type = type; entry->callback = NULL; return 0; } // 0x48859C int nevs_clearevent(const char* a1) { Nevs* entry; debug_printf("nevs_clearevent( '%s');\n", a1); entry = nevs_find(a1); if (entry != NULL) { // NOTE: Uninline. nevs_free(entry); return 0; } return 1; } // 0x48862C int nevs_signal(const char* name) { Nevs* entry; debug_printf("nevs_signal( '%s');\n", name); entry = nevs_find(name); if (entry == NULL) { return 1; } debug_printf("nep: %p, used = %u, prog = %p, proc = %d", entry, entry->used, entry->program, entry->proc); if (entry->used && ((entry->program != NULL && entry->proc != 0) || entry->callback != NULL) && !entry->busy) { entry->hits++; anyhits++; return 0; } return 1; } // 0x4886AC void nevs_update() { int index; Nevs* entry; if (anyhits == 0) { return; } debug_printf("nevs_update(): we have anyhits = %u\n", anyhits); anyhits = 0; for (index = 0; index < NEVS_COUNT; index++) { entry = &(nevs[index]); if (entry->used && ((entry->program != NULL && entry->proc != 0) || entry->callback != NULL) && !entry->busy) { if (entry->hits > 0) { entry->busy = true; entry->hits -= 1; anyhits += entry->hits; if (entry->callback == NULL) { executeProc(entry->program, entry->proc); } else { entry->callback(entry->name); } entry->busy = false; if (entry->type == NEVS_TYPE_EVENT) { // NOTE: Uninline. nevs_free(entry); } } } } } ================================================ FILE: src/int/nevs.h ================================================ #ifndef FALLOUT_INT_NEVS_H_ #define FALLOUT_INT_NEVS_H_ #include #include "int/intrpret.h" typedef void(NevsCallback)(const char* name); typedef enum NevsType { NEVS_TYPE_EVENT = 0, NEVS_TYPE_HANDLER = 1, } NevsType; void nevs_close(); void nevs_initonce(); int nevs_addevent(const char* name, Program* program, int proc, int type); int nevs_addCevent(const char* name, NevsCallback* callback, int type); int nevs_clearevent(const char* name); int nevs_signal(const char* name); void nevs_update(); #endif /* FALLOUT_INT_NEVS_H_ */ ================================================ FILE: src/int/pcx.c ================================================ #include "int/pcx.h" #include "plib/db/db.h" #include "int/memdbg.h" typedef struct PcxHeader { unsigned char identifier; unsigned char version; unsigned char encoding; unsigned char bitsPerPixel; short minX; short minY; short maxX; short maxY; short horizontalResolution; short verticalResolution; unsigned char palette[48]; unsigned char reserved1; unsigned char planeCount; short bytesPerLine; short paletteType; short horizontalScreenSize; short verticalScreenSize; unsigned char reserved2[54]; } PcxHeader; static short getWord(File* stream); static void readPcxHeader(PcxHeader* pcxHeader, File* stream); static int pcxDecodeScanline(unsigned char* data, int size, File* stream); static int readPcxVgaPalette(PcxHeader* pcxHeader, unsigned char* palette, File* stream); // 0x519DC8 static unsigned char runcount = 0; // 0x519DC9 static unsigned char runvalue = 0; // NOTE: Inlined. // // 0x4961B0 static short getWord(File* stream) { short value; db_fread(&value, sizeof(value), 1, stream); return value; } // 0x4961D4 static void readPcxHeader(PcxHeader* pcxHeader, File* stream) { pcxHeader->identifier = db_fgetc(stream); pcxHeader->version = db_fgetc(stream); pcxHeader->encoding = db_fgetc(stream); pcxHeader->bitsPerPixel = db_fgetc(stream); pcxHeader->minX = getWord(stream); pcxHeader->minY = getWord(stream); pcxHeader->maxX = getWord(stream); pcxHeader->maxY = getWord(stream); pcxHeader->horizontalResolution = getWord(stream); pcxHeader->verticalResolution = getWord(stream); for (int index = 0; index < 48; index++) { pcxHeader->palette[index] = db_fgetc(stream); } pcxHeader->reserved1 = db_fgetc(stream); pcxHeader->planeCount = db_fgetc(stream); pcxHeader->bytesPerLine = getWord(stream); pcxHeader->paletteType = getWord(stream); pcxHeader->horizontalScreenSize = getWord(stream); pcxHeader->verticalScreenSize = getWord(stream); for (int index = 0; index < 54; index++) { pcxHeader->reserved2[index] = db_fgetc(stream); } } // 0x49636C static int pcxDecodeScanline(unsigned char* data, int size, File* stream) { unsigned char runLength = runcount; unsigned char value = runvalue; int uncompressedSize = 0; int index = 0; do { uncompressedSize += runLength; while (runLength > 0 && index < size) { data[index] = value; runLength--; index++; } runcount = runLength; runvalue = value; if (runLength != 0) { uncompressedSize -= runLength; break; } value = db_fgetc(stream); if ((value & 0xC0) == 0xC0) { runcount = value & 0x3F; value = db_fgetc(stream); runLength = runcount; } else { runLength = 1; } } while (index < size); runcount = runLength; runvalue = value; return uncompressedSize; } // 0x49641C static int readPcxVgaPalette(PcxHeader* pcxHeader, unsigned char* palette, File* stream) { if (pcxHeader->version != 5) { return 0; } long pos = db_ftell(stream); long size = db_filelength(stream); db_fseek(stream, size - 769, SEEK_SET); if (db_fgetc(stream) != 12) { db_fseek(stream, pos, SEEK_SET); return 0; } for (int index = 0; index < 768; index++) { palette[index] = db_fgetc(stream); } db_fseek(stream, pos, SEEK_SET); return 1; } // 0x496494 unsigned char* loadPCX(const char* path, int* widthPtr, int* heightPtr, unsigned char* palette) { File* stream = db_fopen(path, "rb"); if (stream == NULL) { return NULL; } PcxHeader pcxHeader; readPcxHeader(&pcxHeader, stream); int width = pcxHeader.maxX - pcxHeader.minX + 1; int height = pcxHeader.maxY - pcxHeader.minY + 1; *widthPtr = width; *heightPtr = height; int bytesPerLine = pcxHeader.planeCount * pcxHeader.bytesPerLine; unsigned char* data = mymalloc(bytesPerLine * height, __FILE__, __LINE__); // "..\\int\\PCX.C", 195 if (data == NULL) { // NOTE: This code is unreachable, internal_malloc_safe never fails. db_fclose(stream); return NULL; } runcount = 0; runvalue = 0; unsigned char* ptr = data; for (int y = 0; y < height; y++) { pcxDecodeScanline(ptr, bytesPerLine, stream); ptr += width; } readPcxVgaPalette(&pcxHeader, palette, stream); db_fclose(stream); return data; } ================================================ FILE: src/int/pcx.h ================================================ #ifndef FALLOUT_INT_PCX_H_ #define FALLOUT_INT_PCX_H_ unsigned char* loadPCX(const char* path, int* widthPtr, int* heightPtr, unsigned char* palette); #endif /* FALLOUT_INT_PCX_H_ */ ================================================ FILE: src/int/region.c ================================================ #include "int/region.h" #include #include #include "plib/gnw/debug.h" #include "int/memdbg.h" static_assert(sizeof(Region) == 140, "wrong size"); // 0x4A2B50 void regionSetBound(Region* region) { int minX = INT_MAX; int maxX = INT_MIN; int minY = INT_MAX; int maxY = INT_MIN; int numPoints = 0; int totalX = 0; int totalY = 0; for (int index = 0; index < region->pointsLength; index++) { Point* point = &(region->points[index]); if (minX >= point->x) minX = point->x; if (minY >= point->y) minY = point->y; if (maxX <= point->x) maxX = point->x; if (maxY <= point->y) maxY = point->y; totalX += point->x; totalY += point->y; numPoints++; } region->minY = minY; region->maxX = maxX; region->maxY = maxY; region->minX = minX; if (numPoints != 0) { region->centerX = totalX / numPoints; region->centerY = totalY / numPoints; } } // 0x4A2C14 bool pointInRegion(Region* region, int x, int y) { if (region == NULL) { return false; } if (x < region->minX || x > region->maxX || y < region->minY || y > region->maxY) { return false; } int v1; Point* prev = &(region->points[0]); if (x >= prev->x) { if (y >= prev->y) { v1 = 2; } else { v1 = 1; } } else { if (y >= prev->y) { v1 = 3; } else { v1 = 0; } } int v4 = 0; for (int index = 0; index < region->pointsLength; index++) { int v2; Point* point = &(region->points[index + 1]); if (x >= point->x) { if (y >= point->y) { v2 = 2; } else { v2 = 1; } } else { if (y >= point->y) { v2 = 3; } else { v2 = 0; } } int v3 = v2 - v1; switch (v3) { case -3: v3 = 1; break; case -2: case 2: if ((double)x < ((double)point->x - (double)(prev->x - point->x) / (double)(prev->y - point->y) * (double)(point->y - y))) { v3 = -v3; } break; case 3: v3 = -1; break; } prev = point; v1 = v2; v4 += v3; } if (v4 == 4 || v4 == -4) { return true; } return false; } // 0x4A2D78 Region* allocateRegion(int initialCapacity) { Region* region = (Region*)mymalloc(sizeof(*region), __FILE__, __LINE__); // "..\int\REGION.C", 142 memset(region, 0, sizeof(*region)); if (initialCapacity != 0) { region->points = (Point*)mymalloc(sizeof(*region->points) * (initialCapacity + 1), __FILE__, __LINE__); // "..\int\REGION.C", 147 region->pointsCapacity = initialCapacity + 1; } else { region->points = NULL; region->pointsCapacity = 0; } region->name[0] = '\0'; region->flags = 0; region->minY = INT_MIN; region->maxY = INT_MAX; region->procs[3] = 0; region->rightProcs[1] = 0; region->rightProcs[3] = 0; region->field_68 = 0; region->rightProcs[0] = 0; region->field_70 = 0; region->rightProcs[2] = 0; region->mouseEventCallback = NULL; region->rightMouseEventCallback = NULL; region->mouseEventCallbackUserData = 0; region->rightMouseEventCallbackUserData = 0; region->pointsLength = 0; region->minX = region->minY; region->maxX = region->maxY; region->procs[2] = 0; region->procs[1] = 0; region->procs[0] = 0; region->rightProcs[0] = 0; return region; } // 0x4A2E68 void regionAddPoint(Region* region, int x, int y) { if (region == NULL) { debug_printf("regionAddPoint(): null region ptr\n"); return; } if (region->points != NULL) { if (region->pointsCapacity - 1 == region->pointsLength) { region->points = (Point*)myrealloc(region->points, sizeof(*region->points) * (region->pointsCapacity + 1), __FILE__, __LINE__); // "..\int\REGION.C", 190 region->pointsCapacity++; } } else { region->pointsCapacity = 2; region->pointsLength = 0; region->points = (Point*)mymalloc(sizeof(*region->points) * 2, __FILE__, __LINE__); // "..\int\REGION.C", 185 } int pointIndex = region->pointsLength; region->pointsLength++; Point* point = &(region->points[pointIndex]); point->x = x; point->y = y; Point* end = &(region->points[pointIndex + 1]); end->x = region->points->x; end->y = region->points->y; } // 0x4A2F0C void regionDelete(Region* region) { if (region == NULL) { debug_printf("regionDelete(): null region ptr\n"); return; } if (region->points != NULL) { myfree(region->points, __FILE__, __LINE__); // "..\int\REGION.C", 206 } myfree(region, __FILE__, __LINE__); // "..\int\REGION.C", 207 } // 0x4A2F54 void regionAddName(Region* region, const char* name) { if (region == NULL) { debug_printf("regionAddName(): null region ptr\n"); return; } if (name == NULL) { region->name[0] = '\0'; return; } strncpy(region->name, name, REGION_NAME_LENGTH - 1); } // 0x4A2F80 const char* regionGetName(Region* region) { if (region == NULL) { debug_printf("regionGetName(): null region ptr\n"); return ""; } return region->name; } // 0x4A2F98 void* regionGetUserData(Region* region) { if (region == NULL) { debug_printf("regionGetUserData(): null region ptr\n"); return NULL; } return region->userData; } // 0x4A2FB4 void regionSetUserData(Region* region, void* data) { if (region == NULL) { debug_printf("regionSetUserData(): null region ptr\n"); return; } region->userData = data; } // 0x4A2FD0 void regionSetFlag(Region* region, int value) { region->flags |= value; } // NOTE: Unused. // // 0x4A2FD4 int regionGetFlag(Region* region) { return region->flags; } ================================================ FILE: src/int/region.h ================================================ #ifndef FALLOUT_INT_REGION_H_ #define FALLOUT_INT_REGION_H_ #include "plib/gnw/rect.h" #include "int/intrpret.h" #define REGION_NAME_LENGTH 32 typedef struct Region Region; typedef void RegionMouseEventCallback(Region* region, void* userData, int event); typedef struct Region { char name[REGION_NAME_LENGTH]; Point* points; int minX; int minY; int maxX; int maxY; int centerX; int centerY; int pointsLength; int pointsCapacity; Program* program; int procs[4]; int rightProcs[4]; int field_68; int field_6C; int field_70; int flags; RegionMouseEventCallback* mouseEventCallback; RegionMouseEventCallback* rightMouseEventCallback; void* mouseEventCallbackUserData; void* rightMouseEventCallbackUserData; void* userData; } Region; void regionSetBound(Region* region); bool pointInRegion(Region* region, int x, int y); Region* allocateRegion(int initialCapacity); void regionAddPoint(Region* region, int x, int y); void regionDelete(Region* region); void regionAddName(Region* region, const char* src); const char* regionGetName(Region* region); void* regionGetUserData(Region* region); void regionSetUserData(Region* region, void* data); void regionSetFlag(Region* region, int value); int regionGetFlag(Region* region); #endif /* FALLOUT_INT_REGION_H_ */ ================================================ FILE: src/int/share1.c ================================================ #include "int/share1.h" #include #include #include "plib/db/db.h" static int compare(const void* a1, const void* a2); // 0x4AA250 static int compare(const void* a1, const void* a2) { const char* v1 = *(const char**)a1; const char* v2 = *(const char**)a2; return strcmp(v1, v2); } // 0x4AA2A4 char** getFileList(const char* pattern, int* fileNameListLengthPtr) { char** fileNameList; int fileNameListLength = db_get_file_list(pattern, &fileNameList, 0, 0); *fileNameListLengthPtr = fileNameListLength; if (fileNameListLength == 0) { return NULL; } qsort(fileNameList, fileNameListLength, sizeof(*fileNameList), compare); return fileNameList; } // 0x4AA2DC void freeFileList(char** fileList) { db_free_file_list(&fileList, 0); } ================================================ FILE: src/int/share1.h ================================================ #ifndef FALLOUT_INT_SHARE1_H_ #define FALLOUT_INT_SHARE1_H_ char** getFileList(const char* pattern, int* fileNameListLengthPtr); void freeFileList(char** fileList); #endif /* FALLOUT_INT_SHARE1_H_ */ ================================================ FILE: src/int/sound.c ================================================ #include "int/sound.h" #include #include #include #include #include #include #include "plib/gnw/debug.h" #include "plib/gnw/memory.h" #include "plib/gnw/winmain.h" typedef struct FadeSound { Sound* sound; int deltaVolume; int targetVolume; int initialVolume; int currentVolume; int field_14; struct FadeSound* prev; struct FadeSound* next; } FadeSound; static_assert(sizeof(Sound) == 156, "wrong size"); static void* defaultMalloc(size_t size); static void* defaultRealloc(void* ptr, size_t size); static void defaultFree(void* ptr); static long soundFileSize(int fileHandle); static long soundTellData(int fileHandle); static int soundWriteData(int fileHandle, const void* buf, unsigned int size); static int soundReadData(int fileHandle, void* buf, unsigned int size); static int soundOpenData(const char* filePath, int flags, ...); static int soundSeekData(int fileHandle, long offset, int origin); static int soundCloseData(int fileHandle); static char* defaultMangler(char* fname); static void refreshSoundBuffers(Sound* sound); static int preloadBuffers(Sound* sound); static int addSoundData(Sound* sound, unsigned char* buf, int size); static void CALLBACK doTimerEvent(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2); static void removeTimedEvent(unsigned int* timerId); static void removeFadeSound(FadeSound* fadeSound); static void fadeSounds(); static int internalSoundFade(Sound* sound, int duration, int targetVolume, int a4); // 0x51D478 static FadeSound* fadeHead = NULL; // 0x51D47C static FadeSound* fadeFreeList = NULL; // 0x51D480 static unsigned int fadeEventHandle = UINT_MAX; // 0x51D488 static MallocProc* mallocPtr = defaultMalloc; // 0x51D48C static ReallocProc* reallocPtr = defaultRealloc; // 0x51D490 static FreeProc* freePtr = defaultFree; // 0x51D494 static SoundFileIO defaultStream = { soundOpenData, soundCloseData, soundReadData, soundWriteData, soundSeekData, soundTellData, soundFileSize, -1, }; // 0x51D4B4 static SoundFileNameMangler* nameMangler = defaultMangler; // 0x51D4B8 static const char* errorMsgs[SOUND_ERR_COUNT] = { "sound.c: No error", "sound.c: SOS driver not loaded", "sound.c: SOS invalid pointer", "sound.c: SOS detect initialized", "sound.c: SOS fail on file open", "sound.c: SOS memory fail", "sound.c: SOS invalid driver ID", "sound.c: SOS no driver found", "sound.c: SOS detection failure", "sound.c: SOS driver loaded", "sound.c: SOS invalid handle", "sound.c: SOS no handles", "sound.c: SOS paused", "sound.c: SOS not paused", "sound.c: SOS invalid data", "sound.c: SOS drv file fail", "sound.c: SOS invalid port", "sound.c: SOS invalid IRQ", "sound.c: SOS invalid DMA", "sound.c: SOS invalid DMA IRQ", "sound.c: no device", "sound.c: not initialized", "sound.c: no sound", "sound.c: function not supported", "sound.c: no buffers available", "sound.c: file not found", "sound.c: already playing", "sound.c: not playing", "sound.c: already paused", "sound.c: not paused", "sound.c: invalid handle", "sound.c: no memory available", "sound.c: unknown error", }; // 0x668150 static int soundErrorno; // 0x668154 static int masterVol; // 0x668158 LPDIRECTSOUNDBUFFER primaryDSBuffer; // 0x66815C static int sampleRate; // Number of sounds currently playing. // // 0x668160 static int numSounds; // 0x668164 static int deviceInit; // 0x668168 static int dataSize; // 0x66816C static int numBuffers; // 0x668170 static bool driverInit; // 0x668174 static Sound* soundMgrList; // 0x668178 LPDIRECTSOUND soundDSObject; // 0x4AC6F0 static void* defaultMalloc(size_t size) { return malloc(size); } // 0x4AC6F8 static void* defaultRealloc(void* ptr, size_t size) { return realloc(ptr, size); } // 0x4AC700 static void defaultFree(void* ptr) { free(ptr); } // 0x4AC708 void soundRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) { mallocPtr = mallocProc; reallocPtr = reallocProc; freePtr = freeProc; } // 0x4AC71C static long soundFileSize(int fileHandle) { long pos; long size; pos = tell(fileHandle); size = lseek(fileHandle, 0, SEEK_END); lseek(fileHandle, pos, SEEK_SET); return size; } // 0x4AC750 static long soundTellData(int fileHandle) { return tell(fileHandle); } // 0x4AC758 static int soundWriteData(int fileHandle, const void* buf, unsigned int size) { return write(fileHandle, buf, size); } // 0x4AC760 static int soundReadData(int fileHandle, void* buf, unsigned int size) { return read(fileHandle, buf, size); } // 0x4AC768 static int soundOpenData(const char* filePath, int flags, ...) { return open(filePath, flags); } // 0x4AC774 static int soundSeekData(int fileHandle, long offset, int origin) { return lseek(fileHandle, offset, origin); } // 0x4AC77C static int soundCloseData(int fileHandle) { return close(fileHandle); } // 0x4AC78C static char* defaultMangler(char* fname) { return fname; } // 0x4AC790 const char* soundError(int err) { if (err == -1) { err = soundErrorno; } if (err < 0 || err > SOUND_UNKNOWN_ERROR) { err = SOUND_UNKNOWN_ERROR; } return errorMsgs[err]; } // 0x4AC7B0 static void refreshSoundBuffers(Sound* sound) { if (sound->field_3C & 0x80) { return; } DWORD readPos; DWORD writePos; HRESULT hr = IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos); if (hr != DS_OK) { return; } if (readPos < sound->field_74) { sound->field_64 += readPos + sound->field_78 * sound->field_7C - sound->field_74; } else { sound->field_64 += readPos - sound->field_74; } if (sound->field_3C & 0x0100) { if (sound->field_44 & 0x20) { if (sound->field_3C & 0x0200) { sound->field_3C |= 0x80; } } else { if (sound->field_60 <= sound->field_64) { sound->field_3C |= 0x0280; } } } sound->field_74 = readPos; if (sound->field_60 < sound->field_64) { int v3; do { v3 = sound->field_64 - sound->field_60; sound->field_64 = v3; } while (v3 > sound->field_60); } int v6 = readPos / sound->field_7C; if (sound->field_70 == v6) { return; } int v53; if (sound->field_70 > v6) { v53 = v6 + sound->field_78 - sound->field_70; } else { v53 = v6 - sound->field_70; } if (sound->field_7C * v53 >= sound->readLimit) { v53 = (sound->readLimit + sound->field_7C - 1) / sound->field_7C; } if (v53 < sound->field_5C) { return; } VOID* audioPtr1; VOID* audioPtr2; DWORD audioBytes1; DWORD audioBytes2; hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, sound->field_7C * sound->field_70, sound->field_7C * v53, &audioPtr1, &audioBytes1, &audioPtr2, &audioBytes2, 0); if (hr == DSERR_BUFFERLOST) { IDirectSoundBuffer_Restore(sound->directSoundBuffer); hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, sound->field_7C * sound->field_70, sound->field_7C * v53, &audioPtr1, &audioBytes1, &audioPtr2, &audioBytes2, 0); } if (hr != DS_OK) { return; } if (audioBytes1 + audioBytes2 != sound->field_7C * v53) { debug_printf("locked memory region not big enough, wanted %d (%d * %d), got %d (%d + %d)\n", sound->field_7C * v53, v53, sound->field_7C, audioBytes1 + audioBytes2, audioBytes1, audioBytes2); debug_printf("Resetting readBuffers from %d to %d\n", v53, (audioBytes1 + audioBytes2) / sound->field_7C); v53 = (audioBytes1 + audioBytes2) / sound->field_7C; if (v53 < sound->field_5C) { debug_printf("No longer above read buffer size, returning\n"); return; } } unsigned char* audioPtr = (unsigned char*)audioPtr1; int audioBytes = audioBytes1; while (--v53 != -1) { int bytesRead; if (sound->field_3C & 0x0200) { bytesRead = sound->field_7C; memset(sound->field_20, 0, bytesRead); } else { int bytesToRead = sound->field_7C; if (sound->field_58 != -1) { int pos = sound->io.tell(sound->io.fd); if (bytesToRead + pos > sound->field_58) { bytesToRead = sound->field_58 - pos; } } bytesRead = sound->io.read(sound->io.fd, sound->field_20, bytesToRead); if (bytesRead < sound->field_7C) { if (!(sound->field_3C & 0x20) || (sound->field_3C & 0x0100)) { memset(sound->field_20 + bytesRead, 0, sound->field_7C - bytesRead); sound->field_3C |= 0x0200; bytesRead = sound->field_7C; } else { while (bytesRead < sound->field_7C) { if (sound->field_50 == -1) { sound->io.seek(sound->io.fd, sound->field_54, SEEK_SET); if (sound->callback != NULL) { sound->callback(sound->callbackUserData, 0x0400); } } else { if (sound->field_50 <= 0) { sound->field_58 = -1; sound->field_54 = 0; sound->field_50 = 0; sound->field_3C &= ~0x20; bytesRead += sound->io.read(sound->io.fd, sound->field_20 + bytesRead, sound->field_7C - bytesRead); break; } sound->field_50--; sound->io.seek(sound->io.fd, sound->field_54, SEEK_SET); if (sound->callback != NULL) { sound->callback(sound->callbackUserData, 0x400); } } if (sound->field_58 == -1) { bytesToRead = sound->field_7C - bytesRead; } else { int pos = sound->io.tell(sound->io.fd); if (sound->field_7C + bytesRead + pos <= sound->field_58) { bytesToRead = sound->field_7C - bytesRead; } else { bytesToRead = sound->field_58 - bytesRead - pos; } } int v20 = sound->io.read(sound->io.fd, sound->field_20 + bytesRead, bytesToRead); bytesRead += v20; if (v20 < bytesToRead) { break; } } } } } if (bytesRead > audioBytes) { if (audioBytes != 0) { memcpy(audioPtr, sound->field_20, audioBytes); } if (audioPtr2 != NULL) { memcpy(audioPtr2, sound->field_20 + audioBytes, bytesRead - audioBytes); audioPtr = (unsigned char*)audioPtr2 + bytesRead - audioBytes; audioBytes = audioBytes2 - bytesRead; } else { debug_printf("Hm, no second write pointer, but buffer not big enough, this shouldn't happen\n"); } } else { memcpy(audioPtr, sound->field_20, bytesRead); audioPtr += bytesRead; audioBytes -= bytesRead; } } IDirectSoundBuffer_Unlock(sound->directSoundBuffer, audioPtr1, audioBytes1, audioPtr2, audioBytes2); sound->field_70 = v6; return; } // 0x4ACC58 int soundInit(int a1, int a2, int a3, int a4, int rate) { HRESULT hr; DWORD v24; if (GNW95_DirectSoundCreate(0, &soundDSObject, 0) != DS_OK) { soundDSObject = NULL; soundErrorno = SOUND_SOS_DETECTION_FAILURE; return soundErrorno; } if (IDirectSound_SetCooperativeLevel(soundDSObject, GNW95_hwnd, DSSCL_EXCLUSIVE) != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } sampleRate = rate; dataSize = a4; numBuffers = a2; driverInit = true; deviceInit = 1; DSBUFFERDESC dsbdesc; memset(&dsbdesc, 0, sizeof(dsbdesc)); dsbdesc.dwSize = sizeof(dsbdesc); dsbdesc.dwFlags = DSCAPS_PRIMARYMONO; dsbdesc.dwBufferBytes = 0; hr = IDirectSound_CreateSoundBuffer(soundDSObject, &dsbdesc, &primaryDSBuffer, NULL); if (hr != DS_OK) { switch (hr) { case DSERR_ALLOCATED: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_ALLOCATED"); break; case DSERR_BADFORMAT: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_BADFORMAT"); break; case DSERR_INVALIDPARAM: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_INVALIDPARAM"); break; case DSERR_NOAGGREGATION: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_NOAGGREGATION"); break; case DSERR_OUTOFMEMORY: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_OUTOFMEMORY"); break; case DSERR_UNINITIALIZED: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_UNINITIALIZED"); break; case DSERR_UNSUPPORTED: debug_printf("%s:%s\n", "CreateSoundBuffer", "DSERR_UNSUPPORTED"); break; } exit(1); } WAVEFORMATEX pcmwf; memset(&pcmwf, 0, sizeof(pcmwf)); DSCAPS dscaps; memset(&dscaps, 0, sizeof(dscaps)); dscaps.dwSize = sizeof(dscaps); hr = IDirectSound_GetCaps(soundDSObject, &dscaps); if (hr != DS_OK) { debug_printf("soundInit: Error getting primary buffer parameters\n"); goto out; } pcmwf.nSamplesPerSec = rate; pcmwf.wFormatTag = WAVE_FORMAT_PCM; if (dscaps.dwFlags & DSCAPS_PRIMARY16BIT) { pcmwf.wBitsPerSample = 16; } else { pcmwf.wBitsPerSample = 8; } pcmwf.nChannels = (dscaps.dwFlags & DSCAPS_PRIMARYSTEREO) ? 2 : 1; pcmwf.nBlockAlign = pcmwf.wBitsPerSample * pcmwf.nChannels / 8; pcmwf.nSamplesPerSec = rate; pcmwf.cbSize = 0; pcmwf.nAvgBytesPerSec = pcmwf.nBlockAlign * rate; debug_printf("soundInit: Setting primary buffer to: %d bit, %d channels, %d rate\n", pcmwf.wBitsPerSample, pcmwf.nChannels, rate); hr = IDirectSoundBuffer_SetFormat(primaryDSBuffer, &pcmwf); if (hr != DS_OK) { debug_printf("soundInit: Couldn't change rate to %d\n", rate); switch (hr) { case DSERR_BADFORMAT: debug_printf("%s:%s\n", "SetFormat", "DSERR_BADFORMAT"); break; case DSERR_INVALIDCALL: debug_printf("%s:%s\n", "SetFormat", "DSERR_INVALIDCALL"); break; case DSERR_INVALIDPARAM: debug_printf("%s:%s\n", "SetFormat", "DSERR_INVALIDPARAM"); break; case DSERR_OUTOFMEMORY: debug_printf("%s:%s\n", "SetFormat", "DSERR_OUTOFMEMORY"); break; case DSERR_PRIOLEVELNEEDED: debug_printf("%s:%s\n", "SetFormat", "DSERR_PRIOLEVELNEEDED"); break; case DSERR_UNSUPPORTED: debug_printf("%s:%s\n", "SetFormat", "DSERR_UNSUPPORTED"); break; } goto out; } hr = IDirectSoundBuffer_GetFormat(primaryDSBuffer, &pcmwf, sizeof(WAVEFORMATEX), &v24); if (hr != DS_OK) { debug_printf("soundInit: Couldn't read new settings\n"); goto out; } debug_printf("soundInit: Primary buffer settings set to: %d bit, %d channels, %d rate\n", pcmwf.wBitsPerSample, pcmwf.nChannels, pcmwf.nSamplesPerSec); if (dscaps.dwFlags & DSCAPS_EMULDRIVER) { debug_printf("soundInit: using DirectSound emulated drivers\n"); } out: soundSetMasterVolume(VOLUME_MAX); soundErrorno = SOUND_NO_ERROR; return 0; } // 0x4AD04C void soundClose() { while (soundMgrList != NULL) { Sound* next = soundMgrList->next; soundDelete(soundMgrList); soundMgrList = next; } if (fadeEventHandle != -1) { removeTimedEvent(&fadeEventHandle); } while (fadeFreeList != NULL) { FadeSound* next = fadeFreeList->next; freePtr(fadeFreeList); fadeFreeList = next; } if (primaryDSBuffer != NULL) { IDirectSoundBuffer_Release(primaryDSBuffer); primaryDSBuffer = NULL; } if (soundDSObject != NULL) { IDirectSound_Release(soundDSObject); soundDSObject = NULL; } soundErrorno = SOUND_NO_ERROR; driverInit = false; } // 0x4AD0FC Sound* soundAllocate(int a1, int a2) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return NULL; } Sound* sound = (Sound*)mallocPtr(sizeof(*sound)); memset(sound, 0, sizeof(*sound)); memcpy(&(sound->io), &defaultStream, sizeof(defaultStream)); WAVEFORMATEX* wfxFormat = (WAVEFORMATEX*)mallocPtr(sizeof(*wfxFormat)); memset(wfxFormat, 0, sizeof(*wfxFormat)); wfxFormat->wFormatTag = 1; wfxFormat->nChannels = 1; if (a2 & 0x08) { wfxFormat->wBitsPerSample = 16; } else { wfxFormat->wBitsPerSample = 8; } if (!(a2 & 0x02)) { a2 |= 0x02; } wfxFormat->nSamplesPerSec = sampleRate; wfxFormat->nBlockAlign = wfxFormat->nChannels * (wfxFormat->wBitsPerSample / 8); wfxFormat->cbSize = 0; wfxFormat->nAvgBytesPerSec = wfxFormat->nBlockAlign * wfxFormat->nSamplesPerSec; sound->field_3C = a2; sound->field_44 = a1; sound->field_7C = dataSize; sound->field_64 = 0; sound->directSoundBuffer = 0; sound->field_40 = 0; sound->directSoundBufferDescription.dwSize = sizeof(DSBUFFERDESC); sound->directSoundBufferDescription.dwFlags = DSBCAPS_GETCURRENTPOSITION2; sound->field_78 = numBuffers; sound->readLimit = sound->field_7C * numBuffers; if (a2 & 0x2) { sound->directSoundBufferDescription.dwFlags |= DSBCAPS_CTRLVOLUME; } if (a2 & 0x4) { sound->directSoundBufferDescription.dwFlags |= DSBCAPS_CTRLPAN; } if (a2 & 0x40) { sound->directSoundBufferDescription.dwFlags |= DSBCAPS_CTRLFREQUENCY; } sound->directSoundBufferDescription.lpwfxFormat = wfxFormat; if (a1 & 0x10) { sound->field_50 = -1; sound->field_3C |= 0x20; } sound->field_58 = -1; sound->field_5C = 1; sound->volume = VOLUME_MAX; sound->prev = NULL; sound->field_54 = 0; sound->next = soundMgrList; if (soundMgrList != NULL) { soundMgrList->prev = sound; } soundMgrList = sound; return sound; } // 0x4AD308 static int preloadBuffers(Sound* sound) { unsigned char* buf; int bytes_read; int result; int v15; unsigned char* v14; int size; size = sound->io.filelength(sound->io.fd); sound->field_60 = size; if (sound->field_44 & 0x02) { if (!(sound->field_3C & 0x20)) { sound->field_3C |= 0x0120; } if (sound->field_78 * sound->field_7C >= size) { if (size / sound->field_7C * sound->field_7C != size) { size = (size / sound->field_7C + 1) * sound->field_7C; } } else { size = sound->field_78 * sound->field_7C; } } else { sound->field_44 &= ~(0x03); sound->field_44 |= 0x01; } buf = (unsigned char*)mallocPtr(size); bytes_read = sound->io.read(sound->io.fd, buf, size); if (bytes_read != size) { if (!(sound->field_3C & 0x20) || (sound->field_3C & (0x01 << 8))) { memset(buf + bytes_read, 0, size - bytes_read); } else { v14 = buf + bytes_read; v15 = bytes_read; while (size - v15 > bytes_read) { memcpy(v14, buf, bytes_read); v15 += bytes_read; v14 += bytes_read; } if (v15 < size) { memcpy(v14, buf, size - v15); } } } result = soundSetData(sound, buf, size); freePtr(buf); if (sound->field_44 & 0x01) { sound->io.close(sound->io.fd); sound->io.fd = -1; } else { if (sound->field_20 == NULL) { sound->field_20 = (unsigned char*)mallocPtr(sound->field_7C); } } return result; } // 0x4AD498 int soundLoad(Sound* sound, char* filePath) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } sound->io.fd = sound->io.open(nameMangler(filePath), 0x0200); if (sound->io.fd == -1) { soundErrorno = SOUND_FILE_NOT_FOUND; return soundErrorno; } return preloadBuffers(sound); } // 0x4AD504 int soundRewind(Sound* sound) { HRESULT hr; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sound->field_44 & 0x02) { sound->io.seek(sound->io.fd, 0, SEEK_SET); sound->field_70 = 0; sound->field_74 = 0; sound->field_64 = 0; sound->field_3C &= 0xFD7F; hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, 0); preloadBuffers(sound); } else { hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, 0); } if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } sound->field_40 &= ~SOUND_FLAG_SOUND_IS_DONE; soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AD5C8 static int addSoundData(Sound* sound, unsigned char* buf, int size) { HRESULT hr; void* audio_ptr_1; DWORD audio_bytes_1; void* audio_ptr_2; DWORD audio_bytes_2; hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, 0, size, &audio_ptr_1, &audio_bytes_1, &audio_ptr_2, &audio_bytes_2, DSBLOCK_FROMWRITECURSOR); if (hr == DSERR_BUFFERLOST) { IDirectSoundBuffer_Restore(sound->directSoundBuffer); hr = IDirectSoundBuffer_Lock(sound->directSoundBuffer, 0, size, &audio_ptr_1, &audio_bytes_1, &audio_ptr_2, &audio_bytes_2, DSBLOCK_FROMWRITECURSOR); } if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } memcpy(audio_ptr_1, buf, audio_bytes_1); if (audio_ptr_2 != NULL) { memcpy(audio_ptr_2, buf + audio_bytes_1, audio_bytes_2); } hr = IDirectSoundBuffer_Unlock(sound->directSoundBuffer, audio_ptr_1, audio_bytes_1, audio_ptr_2, audio_bytes_2); if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AD6C0 int soundSetData(Sound* sound, unsigned char* buf, int size) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sound->directSoundBuffer == NULL) { sound->directSoundBufferDescription.dwBufferBytes = size; if (IDirectSound_CreateSoundBuffer(soundDSObject, &(sound->directSoundBufferDescription), &(sound->directSoundBuffer), NULL) != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } } return addSoundData(sound, buf, size); } // 0x4AD73C int soundPlay(Sound* sound) { HRESULT hr; DWORD readPos; DWORD writePos; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } // TODO: Check. if (sound->field_40 & SOUND_FLAG_SOUND_IS_DONE) { soundRewind(sound); } soundVolume(sound, sound->volume); hr = IDirectSoundBuffer_Play(sound->directSoundBuffer, 0, 0, sound->field_3C & 0x20 ? DSBPLAY_LOOPING : 0); IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos); sound->field_70 = readPos / sound->field_7C; if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } sound->field_40 |= SOUND_FLAG_SOUND_IS_PLAYING; ++numSounds; soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AD828 int soundStop(Sound* sound) { HRESULT hr; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING)) { soundErrorno = SOUND_NOT_PLAYING; return soundErrorno; } hr = IDirectSoundBuffer_Stop(sound->directSoundBuffer); if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } sound->field_40 &= ~SOUND_FLAG_SOUND_IS_PLAYING; numSounds--; soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AD8DC int soundDelete(Sound* sample) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sample == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sample->io.fd != -1) { sample->io.close(sample->io.fd); sample->io.fd = -1; } soundMgrDelete(sample); soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AD948 int soundContinue(Sound* sound) { HRESULT hr; DWORD status; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) || (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) { soundErrorno = SOUND_NOT_PLAYING; return soundErrorno; } if (sound->field_40 & SOUND_FLAG_SOUND_IS_DONE) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } hr = IDirectSoundBuffer_GetStatus(sound->directSoundBuffer, &status); if (hr != DS_OK) { debug_printf("Error in soundContinue, %x\n", hr); soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } if (!(sound->field_3C & 0x80) && (status & (DSBSTATUS_PLAYING | DSBSTATUS_LOOPING))) { if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) && (sound->field_44 & 0x02)) { refreshSoundBuffers(sound); } } else if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) { if (sound->callback != NULL) { sound->callback(sound->callbackUserData, 1); sound->callback = NULL; } if (sound->field_44 & 0x04) { sound->callback = NULL; soundDelete(sound); } else { sound->field_40 |= SOUND_FLAG_SOUND_IS_DONE; if (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) { --numSounds; } soundStop(sound); sound->field_40 &= ~(SOUND_FLAG_SOUND_IS_DONE | SOUND_FLAG_SOUND_IS_PLAYING); } } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4ADA84 bool soundPlaying(Sound* sound) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return false; } if (sound == NULL || sound->directSoundBuffer == 0) { soundErrorno = SOUND_NO_SOUND; return false; } return (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) != 0; } // 0x4ADAC4 bool soundDone(Sound* sound) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return false; } if (sound == NULL || sound->directSoundBuffer == 0) { soundErrorno = SOUND_NO_SOUND; return false; } return sound->field_40 & 1; } // 0x4ADB44 bool soundPaused(Sound* sound) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return false; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return false; } return (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) != 0; } // 0x4ADBC4 int soundType(Sound* sound, int a2) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return 0; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return 0; } return sound->field_44 & a2; } // 0x4ADC04 int soundLength(Sound* sound) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } int bytesPerSec = sound->directSoundBufferDescription.lpwfxFormat->nAvgBytesPerSec; int v3 = sound->field_60; int v4 = v3 % bytesPerSec; int result = v3 / bytesPerSec; if (v4 != 0) { result += 1; } return result; } // 0x4ADD00 int soundLoop(Sound* sound, int a2) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (a2) { sound->field_3C |= 0x20; sound->field_50 = a2; } else { sound->field_50 = 0; sound->field_58 = -1; sound->field_54 = 0; sound->field_3C &= ~(0x20); } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // Normalize volume? // // 0x4ADD68 int soundVolumeHMItoDirectSound(int volume) { double normalizedVolume; if (volume > VOLUME_MAX) { volume = VOLUME_MAX; } if (volume != 0) { normalizedVolume = -1000.0 * log2(32767.0 / volume); normalizedVolume = max(min(normalizedVolume, 0.0), -10000.0); } else { normalizedVolume = -10000.0; } return (int)normalizedVolume; } // 0x4ADE0C int soundVolume(Sound* sound, int volume) { int normalizedVolume; HRESULT hr; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } sound->volume = volume; if (sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_ERROR; return soundErrorno; } normalizedVolume = soundVolumeHMItoDirectSound(masterVol * volume / VOLUME_MAX); hr = IDirectSoundBuffer_SetVolume(sound->directSoundBuffer, normalizedVolume); if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4ADE80 int soundGetVolume(Sound* sound) { long volume; int v13; int v8; int diff; if (!deviceInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } IDirectSoundBuffer_GetVolume(sound->directSoundBuffer, &volume); if (volume == -10000) { v13 = 0; } else { // TODO: Check. volume = -volume; v13 = (int)(32767.0 / pow(2.0, (volume * 0.001))); } v8 = VOLUME_MAX * v13 / masterVol; diff = abs(v8 - sound->volume); if (diff > 20) { debug_printf("Actual sound volume differs significantly from noted volume actual %x stored %x, diff %d.", v8, sound->volume, diff); } return sound->volume; } // 0x4ADFF0 int soundSetCallback(Sound* sound, SoundCallback* callback, void* userData) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } sound->callback = callback; sound->callbackUserData = userData; soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AE02C int soundSetChannel(Sound* sound, int channels) { LPWAVEFORMATEX format; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (channels == 3) { format = sound->directSoundBufferDescription.lpwfxFormat; format->nBlockAlign = (2 * format->wBitsPerSample) / 8; format->nChannels = 2; format->nAvgBytesPerSec = format->nBlockAlign * sampleRate; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AE0B0 int soundSetReadLimit(Sound* sound, int readLimit) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_DEVICE; return soundErrorno; } sound->readLimit = readLimit; soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // TODO: Check, looks like it uses couple of inlined functions. // // 0x4AE0E4 int soundPause(Sound* sound) { HRESULT hr; DWORD readPos; DWORD writePos; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING)) { soundErrorno = SOUND_NOT_PLAYING; return soundErrorno; } if (sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED) { soundErrorno = SOUND_ALREADY_PAUSED; return soundErrorno; } hr = IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &readPos, &writePos); if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } sound->field_48 = readPos; sound->field_40 |= SOUND_FLAG_SOUND_IS_PAUSED; return soundStop(sound); } // TODO: Check, looks like it uses couple of inlined functions. // // 0x4AE1F0 int soundUnpause(Sound* sound) { HRESULT hr; if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL || sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if ((sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) != 0) { soundErrorno = SOUND_NOT_PAUSED; return soundErrorno; } if (!(sound->field_40 & SOUND_FLAG_SOUND_IS_PAUSED)) { soundErrorno = SOUND_NOT_PAUSED; return soundErrorno; } hr = IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, sound->field_48); if (hr != DS_OK) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } sound->field_40 &= ~SOUND_FLAG_SOUND_IS_PAUSED; sound->field_48 = 0; return soundPlay(sound); } // 0x4AE2FC int soundSetFileIO(Sound* sound, SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (openProc != NULL) { sound->io.open = openProc; } if (closeProc != NULL) { sound->io.close = closeProc; } if (readProc != NULL) { sound->io.read = readProc; } if (writeProc != NULL) { sound->io.write = writeProc; } if (seekProc != NULL) { sound->io.seek = seekProc; } if (tellProc != NULL) { sound->io.tell = tellProc; } if (fileLengthProc != NULL) { sound->io.filelength = fileLengthProc; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AE378 void soundMgrDelete(Sound* sound) { FadeSound* curr; Sound* v10; Sound* v11; if (sound->field_40 & SOUND_FLAG_SOUND_IS_FADING) { curr = fadeHead; while (curr != NULL) { if (sound == curr->sound) { break; } curr = curr->next; } removeFadeSound(curr); } if (sound->directSoundBuffer != NULL) { // NOTE: Uninline. if (!soundPlaying(sound)) { soundStop(sound); } if (sound->callback != NULL) { sound->callback(sound->callbackUserData, 1); } IDirectSoundBuffer_Release(sound->directSoundBuffer); sound->directSoundBuffer = NULL; } if (sound->field_90 != NULL) { sound->field_90(sound->field_8C); } if (sound->field_20 != NULL) { freePtr(sound->field_20); sound->field_20 = NULL; } if (sound->directSoundBufferDescription.lpwfxFormat != NULL) { freePtr(sound->directSoundBufferDescription.lpwfxFormat); } v10 = sound->next; if (v10 != NULL) { v10->prev = sound->prev; } v11 = sound->prev; if (v11 != NULL) { v11->next = sound->next; } else { soundMgrList = sound->next; } freePtr(sound); } // 0x4AE578 int soundSetMasterVolume(int volume) { if (volume < VOLUME_MIN || volume > VOLUME_MAX) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } masterVol = volume; Sound* curr = soundMgrList; while (curr != NULL) { soundVolume(curr, curr->volume); curr = curr->next; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AE5C8 static void CALLBACK doTimerEvent(UINT uTimerID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2) { void (*fn)(); if (dwUser != 0) { fn = (void (*)())dwUser; fn(); } } // 0x4AE614 static void removeTimedEvent(unsigned int* timerId) { if (*timerId != -1) { timeKillEvent(*timerId); *timerId = -1; } } // 0x4AE634 int soundGetPosition(Sound* sound) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } DWORD playPos; DWORD writePos; IDirectSoundBuffer_GetCurrentPosition(sound->directSoundBuffer, &playPos, &writePos); if ((sound->field_44 & 0x02) != 0) { if (playPos < sound->field_74) { playPos += sound->field_64 + sound->field_78 * sound->field_7C - sound->field_74; } else { playPos -= sound->field_74 + sound->field_64; } } return playPos; } // 0x4AE6CC int soundSetPosition(Sound* sound, int a2) { if (!driverInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sound->directSoundBuffer == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } if (sound->field_44 & 0x02) { int v6 = a2 / sound->field_7C % sound->field_78; IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, v6 * sound->field_7C + a2 % sound->field_7C); sound->io.seek(sound->io.fd, v6 * sound->field_7C, SEEK_SET); int bytes_read = sound->io.read(sound->io.fd, sound->field_20, sound->field_7C); if (bytes_read < sound->field_7C) { if (sound->field_44 & 0x02) { sound->io.seek(sound->io.fd, 0, SEEK_SET); sound->io.read(sound->io.fd, sound->field_20 + bytes_read, sound->field_7C - bytes_read); } else { memset(sound->field_20 + bytes_read, 0, sound->field_7C - bytes_read); } } int v17 = v6 + 1; sound->field_64 = a2; if (v17 < sound->field_78) { sound->field_70 = v17; } else { sound->field_70 = 0; } soundContinue(sound); } else { IDirectSoundBuffer_SetCurrentPosition(sound->directSoundBuffer, a2); } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AE830 static void removeFadeSound(FadeSound* fadeSound) { FadeSound* prev; FadeSound* next; FadeSound* tmp; if (fadeSound == NULL) { return; } if (fadeSound->sound == NULL) { return; } if (!(fadeSound->sound->field_40 & SOUND_FLAG_SOUND_IS_FADING)) { return; } prev = fadeSound->prev; if (prev != NULL) { prev->next = fadeSound->next; } else { fadeHead = fadeSound->next; } next = fadeSound->next; if (next != NULL) { next->prev = fadeSound->prev; } fadeSound->sound->field_40 &= ~SOUND_FLAG_SOUND_IS_FADING; fadeSound->sound = NULL; tmp = fadeFreeList; fadeFreeList = fadeSound; fadeSound->next = tmp; } // 0x4AE8B0 static void fadeSounds() { FadeSound* ptr; ptr = fadeHead; while (ptr != NULL) { if ((ptr->currentVolume > ptr->targetVolume || ptr->currentVolume + ptr->deltaVolume < ptr->targetVolume) && (ptr->currentVolume < ptr->targetVolume || ptr->currentVolume + ptr->deltaVolume > ptr->targetVolume)) { ptr->currentVolume += ptr->deltaVolume; soundVolume(ptr->sound, ptr->currentVolume); } else { if (ptr->targetVolume == 0) { if (ptr->field_14) { soundPause(ptr->sound); soundVolume(ptr->sound, ptr->initialVolume); } else { if (ptr->sound->field_44 & 0x04) { soundDelete(ptr->sound); } else { soundStop(ptr->sound); ptr->initialVolume = ptr->targetVolume; ptr->currentVolume = ptr->targetVolume; ptr->deltaVolume = 0; soundVolume(ptr->sound, ptr->targetVolume); } } } removeFadeSound(ptr); } } if (fadeHead == NULL) { // NOTE: Uninline. removeTimedEvent(&fadeEventHandle); } } // 0x4AE988 static int internalSoundFade(Sound* sound, int duration, int targetVolume, int a4) { FadeSound* ptr; if (!deviceInit) { soundErrorno = SOUND_NOT_INITIALIZED; return soundErrorno; } if (sound == NULL) { soundErrorno = SOUND_NO_SOUND; return soundErrorno; } ptr = NULL; if (sound->field_40 & SOUND_FLAG_SOUND_IS_FADING) { ptr = fadeHead; while (ptr != NULL) { if (ptr->sound == sound) { break; } ptr = ptr->next; } } if (ptr == NULL) { if (fadeFreeList != NULL) { ptr = fadeFreeList; fadeFreeList = fadeFreeList->next; } else { ptr = (FadeSound*)mallocPtr(sizeof(FadeSound)); } if (ptr != NULL) { if (fadeHead != NULL) { fadeHead->prev = ptr; } ptr->sound = sound; ptr->prev = NULL; ptr->next = fadeHead; fadeHead = ptr; } } if (ptr == NULL) { soundErrorno = SOUND_NO_MEMORY_AVAILABLE; return soundErrorno; } ptr->targetVolume = targetVolume; ptr->initialVolume = soundGetVolume(sound); ptr->currentVolume = ptr->initialVolume; ptr->field_14 = a4; // TODO: Check. ptr->deltaVolume = 8 * (125 * (targetVolume - ptr->initialVolume)) / (40 * duration); sound->field_40 |= SOUND_FLAG_SOUND_IS_FADING; bool v14; if (driverInit) { if (sound->directSoundBuffer != NULL) { v14 = (sound->field_40 & SOUND_FLAG_SOUND_IS_PLAYING) == 0; } else { soundErrorno = SOUND_NO_SOUND; v14 = true; } } else { soundErrorno = SOUND_NOT_INITIALIZED; v14 = true; } if (v14) { soundPlay(sound); } if (fadeEventHandle != -1) { soundErrorno = SOUND_NO_ERROR; return soundErrorno; } fadeEventHandle = timeSetEvent(40, 10, doTimerEvent, (DWORD_PTR)fadeSounds, 1); if (fadeEventHandle == 0) { soundErrorno = SOUND_UNKNOWN_ERROR; return soundErrorno; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } // 0x4AEB0C int soundFade(Sound* sound, int duration, int targetVolume) { return internalSoundFade(sound, duration, targetVolume, 0); } // 0x4AEB54 void soundFlushAllSounds() { while (soundMgrList != NULL) { soundDelete(soundMgrList); } } // 0x4AEBE0 void soundContinueAll() { Sound* curr = soundMgrList; while (curr != NULL) { // Sound can be deallocated in `soundContinue`. Sound* next = curr->next; soundContinue(curr); curr = next; } } // 0x4AEC00 int soundSetDefaultFileIO(SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc) { if (openProc != NULL) { defaultStream.open = openProc; } if (closeProc != NULL) { defaultStream.close = closeProc; } if (readProc != NULL) { defaultStream.read = readProc; } if (writeProc != NULL) { defaultStream.write = writeProc; } if (seekProc != NULL) { defaultStream.seek = seekProc; } if (tellProc != NULL) { defaultStream.tell = tellProc; } if (fileLengthProc != NULL) { defaultStream.filelength = fileLengthProc; } soundErrorno = SOUND_NO_ERROR; return soundErrorno; } ================================================ FILE: src/int/sound.h ================================================ #ifndef FALLOUT_INT_SOUND_H_ #define FALLOUT_INT_SOUND_H_ #include #include "memory_defs.h" #include "plib/gnw/gnw95dx.h" #define SOUND_FLAG_SOUND_IS_DONE 0x01 #define SOUND_FLAG_SOUND_IS_PLAYING 0x02 #define SOUND_FLAG_SOUND_IS_FADING 0x04 #define SOUND_FLAG_SOUND_IS_PAUSED 0x08 #define VOLUME_MIN 0 #define VOLUME_MAX 0x7FFF typedef enum SoundError { SOUND_NO_ERROR = 0, SOUND_SOS_DRIVER_NOT_LOADED = 1, SOUND_SOS_INVALID_POINTER = 2, SOUND_SOS_DETECT_INITIALIZED = 3, SOUND_SOS_FAIL_ON_FILE_OPEN = 4, SOUND_SOS_MEMORY_FAIL = 5, SOUND_SOS_INVALID_DRIVER_ID = 6, SOUND_SOS_NO_DRIVER_FOUND = 7, SOUND_SOS_DETECTION_FAILURE = 8, SOUND_SOS_DRIVER_LOADED = 9, SOUND_SOS_INVALID_HANDLE = 10, SOUND_SOS_NO_HANDLES = 11, SOUND_SOS_PAUSED = 12, SOUND_SOS_NO_PAUSED = 13, SOUND_SOS_INVALID_DATA = 14, SOUND_SOS_DRV_FILE_FAIL = 15, SOUND_SOS_INVALID_PORT = 16, SOUND_SOS_INVALID_IRQ = 17, SOUND_SOS_INVALID_DMA = 18, SOUND_SOS_INVALID_DMA_IRQ = 19, SOUND_NO_DEVICE = 20, SOUND_NOT_INITIALIZED = 21, SOUND_NO_SOUND = 22, SOUND_FUNCTION_NOT_SUPPORTED = 23, SOUND_NO_BUFFERS_AVAILABLE = 24, SOUND_FILE_NOT_FOUND = 25, SOUND_ALREADY_PLAYING = 26, SOUND_NOT_PLAYING = 27, SOUND_ALREADY_PAUSED = 28, SOUND_NOT_PAUSED = 29, SOUND_INVALID_HANDLE = 30, SOUND_NO_MEMORY_AVAILABLE = 31, SOUND_UNKNOWN_ERROR = 32, SOUND_ERR_COUNT, } SoundError; typedef char*(SoundFileNameMangler)(char*); typedef int SoundOpenProc(const char* filePath, int flags, ...); typedef int SoundCloseProc(int fileHandle); typedef int SoundReadProc(int fileHandle, void* buf, unsigned int size); typedef int SoundWriteProc(int fileHandle, const void* buf, unsigned int size); typedef long SoundSeekProc(int fileHandle, long offset, int origin); typedef long SoundTellProc(int fileHandle); typedef long SoundFileLengthProc(int fileHandle); typedef struct SoundFileIO { SoundOpenProc* open; SoundCloseProc* close; SoundReadProc* read; SoundWriteProc* write; SoundSeekProc* seek; SoundTellProc* tell; SoundFileLengthProc* filelength; int fd; } SoundFileIO; typedef void SoundCallback(void* userData, int a2); typedef struct Sound { SoundFileIO io; unsigned char* field_20; LPDIRECTSOUNDBUFFER directSoundBuffer; DSBUFFERDESC directSoundBufferDescription; int field_3C; // flags int field_40; int field_44; // pause pos int field_48; int volume; int field_50; int field_54; int field_58; int field_5C; // file size int field_60; int field_64; int field_68; int readLimit; int field_70; DWORD field_74; int field_78; int field_7C; int field_80; // callback data void* callbackUserData; SoundCallback* callback; int field_8C; void (*field_90)(int); struct Sound* next; struct Sound* prev; } Sound; extern LPDIRECTSOUNDBUFFER primaryDSBuffer; extern LPDIRECTSOUND soundDSObject; void soundRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); const char* soundError(int err); int soundInit(int a1, int a2, int a3, int a4, int rate); void soundClose(); Sound* soundAllocate(int a1, int a2); int soundLoad(Sound* sound, char* filePath); int soundRewind(Sound* sound); int soundSetData(Sound* sound, unsigned char* buf, int size); int soundPlay(Sound* sound); int soundStop(Sound* sound); int soundDelete(Sound* sound); int soundContinue(Sound* sound); bool soundPlaying(Sound* sound); bool soundDone(Sound* sound); bool soundPaused(Sound* sound); int soundType(Sound* sound, int a2); int soundLength(Sound* sound); int soundLoop(Sound* sound, int a2); int soundVolumeHMItoDirectSound(int a1); int soundVolume(Sound* sound, int volume); int soundGetVolume(Sound* sound); int soundSetCallback(Sound* sound, SoundCallback* callback, void* userData); int soundSetChannel(Sound* sound, int channels); int soundSetReadLimit(Sound* sound, int readLimit); int soundPause(Sound* sound); int soundUnpause(Sound* sound); int soundSetFileIO(Sound* sound, SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc); void soundMgrDelete(Sound* sound); int soundSetMasterVolume(int value); int soundGetPosition(Sound* sound); int soundSetPosition(Sound* sound, int a2); int soundFade(Sound* sound, int duration, int targetVolume); void soundFlushAllSounds(); void soundContinueAll(); int soundSetDefaultFileIO(SoundOpenProc* openProc, SoundCloseProc* closeProc, SoundReadProc* readProc, SoundWriteProc* writeProc, SoundSeekProc* seekProc, SoundTellProc* tellProc, SoundFileLengthProc* fileLengthProc); #endif /* FALLOUT_INT_SOUND_H_ */ ================================================ FILE: src/int/support/intextra.c ================================================ #include "int/support/intextra.h" #include #include #include #include "game/actions.h" #include "game/anim.h" #include "plib/color/color.h" #include "game/combat.h" #include "game/combatai.h" #include "plib/gnw/input.h" #include "game/critter.h" #include "plib/gnw/debug.h" #include "int/dialog.h" #include "game/display.h" #include "game/endgame.h" #include "game/game.h" #include "game/gconfig.h" #include "game/gdialog.h" #include "game/gmovie.h" #include "game/gsound.h" #include "plib/gnw/rect.h" #include "game/intface.h" #include "game/item.h" #include "game/light.h" #include "game/loadsave.h" #include "game/map.h" #include "game/object.h" #include "game/palette.h" #include "game/perk.h" #include "game/proto.h" #include "game/protinst.h" #include "game/queue.h" #include "game/roll.h" #include "game/reaction.h" #include "game/scripts.h" #include "game/skill.h" #include "game/stat.h" #include "game/textobj.h" #include "game/tile.h" #include "game/trait.h" #include "plib/gnw/vcr.h" #include "game/worldmap.h" typedef enum Metarule { METARULE_SIGNAL_END_GAME = 13, METARULE_FIRST_RUN = 14, METARULE_ELEVATOR = 15, METARULE_PARTY_COUNT = 16, METARULE_AREA_KNOWN = 17, METARULE_WHO_ON_DRUGS = 18, METARULE_MAP_KNOWN = 19, METARULE_IS_LOADGAME = 22, METARULE_CAR_CURRENT_TOWN = 30, METARULE_GIVE_CAR_TO_PARTY = 31, METARULE_GIVE_CAR_GAS = 32, METARULE_SKILL_CHECK_TAG = 40, METARULE_DROP_ALL_INVEN = 42, METARULE_INVEN_UNWIELD_WHO = 43, METARULE_GET_WORLDMAP_XPOS = 44, METARULE_GET_WORLDMAP_YPOS = 45, METARULE_CURRENT_TOWN = 46, METARULE_LANGUAGE_FILTER = 47, METARULE_VIOLENCE_FILTER = 48, METARULE_WEAPON_DAMAGE_TYPE = 49, METARULE_CRITTER_BARTERS = 50, METARULE_CRITTER_KILL_TYPE = 51, METARULE_SET_CAR_CARRY_AMOUNT = 52, METARULE_GET_CAR_CARRY_AMOUNT = 53, } Metarule; typedef enum Metarule3 { METARULE3_CLR_FIXED_TIMED_EVENTS = 100, METARULE3_MARK_SUBTILE = 101, METARULE3_SET_WM_MUSIC = 102, METARULE3_GET_KILL_COUNT = 103, METARULE3_MARK_MAP_ENTRANCE = 104, METARULE3_WM_SUBTILE_STATE = 105, METARULE3_TILE_GET_NEXT_CRITTER = 106, METARULE3_ART_SET_BASE_FID_NUM = 107, METARULE3_TILE_SET_CENTER = 108, // chem use preference METARULE3_109 = 109, // probably true if car is out of fuel METARULE3_110 = 110, // probably returns city index METARULE3_111 = 111, } Metarule3; typedef enum CritterTrait { CRITTER_TRAIT_PERK = 0, CRITTER_TRAIT_OBJECT = 1, CRITTER_TRAIT_TRAIT = 2, } CritterTrait; typedef enum CritterTraitObject { CRITTER_TRAIT_OBJECT_AI_PACKET = 5, CRITTER_TRAIT_OBJECT_TEAM = 6, CRITTER_TRAIT_OBJECT_ROTATION = 10, CRITTER_TRAIT_OBJECT_IS_INVISIBLE = 666, CRITTER_TRAIT_OBJECT_GET_INVENTORY_WEIGHT = 669, } CritterTraitObject; // See [op_critter_state]. typedef enum CritterState { CRITTER_STATE_NORMAL = 0x00, CRITTER_STATE_DEAD = 0x01, CRITTER_STATE_PRONE = 0x02, } CritterState; enum { INVEN_TYPE_WORN = 0, INVEN_TYPE_RIGHT_HAND = 1, INVEN_TYPE_LEFT_HAND = 2, INVEN_TYPE_INV_COUNT = -2, }; typedef enum FloatingMessageType { FLOATING_MESSAGE_TYPE_WARNING = -2, FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE = -1, FLOATING_MESSAGE_TYPE_NORMAL = 0, FLOATING_MESSAGE_TYPE_BLACK, FLOATING_MESSAGE_TYPE_RED, FLOATING_MESSAGE_TYPE_GREEN, FLOATING_MESSAGE_TYPE_BLUE, FLOATING_MESSAGE_TYPE_PURPLE, FLOATING_MESSAGE_TYPE_NEAR_WHITE, FLOATING_MESSAGE_TYPE_LIGHT_RED, FLOATING_MESSAGE_TYPE_YELLOW, FLOATING_MESSAGE_TYPE_WHITE, FLOATING_MESSAGE_TYPE_GREY, FLOATING_MESSAGE_TYPE_DARK_GREY, FLOATING_MESSAGE_TYPE_LIGHT_GREY, FLOATING_MESSAGE_TYPE_COUNT, } FloatingMessageType; typedef enum OpRegAnimFunc { OP_REG_ANIM_FUNC_BEGIN = 1, OP_REG_ANIM_FUNC_CLEAR = 2, OP_REG_ANIM_FUNC_END = 3, } OpRegAnimFunc; static void int_debug(const char* format, ...); static int scripts_tile_is_visible(int tile); static int correctFidForRemovedItem(Object* a1, Object* a2, int a3); static void op_give_exp_points(Program* program); static void op_scr_return(Program* program); static void op_play_sfx(Program* program); static void op_set_map_start(Program* program); static void op_override_map_start(Program* program); static void op_has_skill(Program* program); static void op_using_skill(Program* program); static void op_roll_vs_skill(Program* program); static void op_skill_contest(Program* program); static void op_do_check(Program* program); static void op_is_success(Program* program); static void op_is_critical(Program* program); static void op_how_much(Program* program); static void op_mark_area_known(Program* program); static void op_reaction_influence(Program* program); static void op_random(Program* program); static void op_roll_dice(Program* program); static void op_move_to(Program* program); static void op_create_object_sid(Program* program); static void op_destroy_object(Program* program); static void op_display_msg(Program* program); static void op_script_overrides(Program* program); static void op_obj_is_carrying_obj_pid(Program* program); static void op_tile_contains_obj_pid(Program* program); static void op_self_obj(Program* program); static void op_source_obj(Program* program); static void op_target_obj(Program* program); static void op_dude_obj(Program* program); static void op_obj_being_used_with(Program* program); static void op_local_var(Program* program); static void op_set_local_var(Program* program); static void op_map_var(Program* program); static void op_set_map_var(Program* program); static void op_global_var(Program* program); static void op_set_global_var(Program* program); static void op_script_action(Program* program); static void op_obj_type(Program* program); static void op_obj_item_subtype(Program* program); static void op_get_critter_stat(Program* program); static void op_set_critter_stat(Program* program); static void op_animate_stand_obj(Program* program); static void op_animate_stand_reverse_obj(Program* program); static void op_animate_move_obj_to_tile(Program* program); static void op_tile_in_tile_rect(Program* program); static void op_make_daytime(Program* program); static void op_tile_distance(Program* program); static void op_tile_distance_objs(Program* program); static void op_tile_num(Program* program); static void op_tile_num_in_direction(Program* program); static void op_pickup_obj(Program* program); static void op_drop_obj(Program* program); static void op_add_obj_to_inven(Program* program); static void op_rm_obj_from_inven(Program* program); static void op_wield_obj_critter(Program* program); static void op_use_obj(Program* program); static void op_obj_can_see_obj(Program* program); static void op_attack(Program* program); static void op_start_gdialog(Program* program); static void op_end_dialogue(Program* program); static void op_dialogue_reaction(Program* program); static void op_metarule3(Program* program); static void op_set_map_music(Program* program); static void op_set_obj_visibility(Program* program); static void op_load_map(Program* program); static void op_wm_area_set_pos(Program* program); static void op_set_exit_grids(Program* program); static void op_anim_busy(Program* program); static void op_critter_heal(Program* program); static void op_set_light_level(Program* program); static void op_game_time(Program* program); static void op_game_time_in_seconds(Program* program); static void op_elevation(Program* program); static void op_kill_critter(Program* program); static void op_kill_critter_type(Program* program); static void op_critter_damage(Program* program); static void op_add_timer_event(Program* program); static void op_rm_timer_event(Program* program); static void op_game_ticks(Program* program); static void op_has_trait(Program* program); static void op_obj_can_hear_obj(Program* program); static void op_game_time_hour(Program* program); static void op_fixed_param(Program* program); static void op_tile_is_visible(Program* program); static void op_dialogue_system_enter(Program* program); static void op_action_being_used(Program* program); static void op_critter_state(Program* program); static void op_game_time_advance(Program* program); static void op_radiation_inc(Program* program); static void op_radiation_dec(Program* program); static void op_critter_attempt_placement(Program* program); static void op_obj_pid(Program* program); static void op_cur_map_index(Program* program); static void op_critter_add_trait(Program* program); static void op_critter_rm_trait(Program* program); static void op_proto_data(Program* program); static void op_message_str(Program* program); static void op_critter_inven_obj(Program* program); static void op_obj_set_light_level(Program* program); static void op_world_map(Program* program); static void op_inven_cmds(Program* program); static void op_float_msg(Program* program); static void op_metarule(Program* program); static void op_anim(Program* program); static void op_obj_carrying_pid_obj(Program* program); static void op_reg_anim_func(Program* program); static void op_reg_anim_animate(Program* program); static void op_reg_anim_animate_reverse(Program* program); static void op_reg_anim_obj_move_to_obj(Program* program); static void op_reg_anim_obj_run_to_obj(Program* program); static void op_reg_anim_obj_move_to_tile(Program* program); static void op_reg_anim_obj_run_to_tile(Program* program); static void op_play_gmovie(Program* program); static void op_add_mult_objs_to_inven(Program* program); static void op_rm_mult_objs_from_inven(Program* program); static void op_get_month(Program* program); static void op_get_day(Program* program); static void op_explosion(Program* program); static void op_days_since_visited(Program* program); static void op_gsay_start(Program* program); static void op_gsay_end(Program* program); static void op_gsay_reply(Program* program); static void op_gsay_option(Program* program); static void op_gsay_message(Program* program); static void op_giq_option(Program* program); static void op_poison(Program* program); static void op_get_poison(Program* program); static void op_party_add(Program* program); static void op_party_remove(Program* program); static void op_reg_anim_animate_forever(Program* program); static void op_critter_injure(Program* program); static void op_combat_is_initialized(Program* program); static void op_gdialog_barter(Program* program); static void op_difficulty_level(Program* program); static void op_running_burning_guy(Program* program); static void op_inven_unwield(Program* program); static void op_obj_is_locked(Program* program); static void op_obj_lock(Program* program); static void op_obj_unlock(Program* program); static void op_obj_is_open(Program* program); static void op_obj_open(Program* program); static void op_obj_close(Program* program); static void op_game_ui_disable(Program* program); static void op_game_ui_enable(Program* program); static void op_game_ui_is_disabled(Program* program); static void op_gfade_out(Program* program); static void op_gfade_in(Program* program); static void op_item_caps_total(Program* program); static void op_item_caps_adjust(Program* program); static void op_anim_action_frame(Program* program); static void op_reg_anim_play_sfx(Program* program); static void op_critter_mod_skill(Program* program); static void op_sfx_build_char_name(Program* program); static void op_sfx_build_ambient_name(Program* program); static void op_sfx_build_interface_name(Program* program); static void op_sfx_build_item_name(Program* program); static void op_sfx_build_weapon_name(Program* program); static void op_sfx_build_scenery_name(Program* program); static void op_sfx_build_open_name(Program* program); static void op_attack_setup(Program* program); static void op_destroy_mult_objs(Program* program); static void op_use_obj_on_obj(Program* program); static void op_endgame_slideshow(Program* program); static void op_move_obj_inven_to_obj(Program* program); static void op_endgame_movie(Program* program); static void op_obj_art_fid(Program* program); static void op_art_anim(Program* program); static void op_party_member_obj(Program* program); static void op_rotation_to_tile(Program* program); static void op_jam_lock(Program* program); static void op_gdialog_set_barter_mod(Program* program); static void op_combat_difficulty(Program* program); static void op_obj_on_screen(Program* program); static void op_critter_is_fleeing(Program* program); static void op_critter_set_flee_state(Program* program); static void op_terminate_combat(Program* program); static void op_debug_msg(Program* program); static void op_critter_stop_attacking(Program* program); static void op_tile_contains_pid_obj(Program* program); static void op_obj_name(Program* program); static void op_get_pc_stat(Program* program); // TODO: Remove. // 0x504B0C char _aCritter[] = ""; // NOTE: This value is a little bit odd. It's used to handle 2 operations: // [op_start_gdialog] and [op_dialogue_reaction]. It's not used outside those // functions. // // When used inside [op_start_gdialog] this value stores [Fidget] constant // (1 - Good, 4 - Neutral, 7 - Bad). // // When used inside [op_dialogue_reaction] this value contains specified // reaction (-1 - Good, 0 - Neutral, 1 - Bad). // // 0x5970D0 static int dialogue_mood; // 0x453FD0 void dbg_error(Program* program, const char* name, int error) { // 0x518EC0 static const char* dbg_error_strs[SCRIPT_ERROR_COUNT] = { "unimped", "obj is NULL", "can't match program to sid", "follows", }; char string[260]; sprintf(string, "Script Error: %s: op_%s: %s", program->name, name, dbg_error_strs[error]); debug_printf(string); } // 0x45400C static void int_debug(const char* format, ...) { char string[260]; va_list argptr; va_start(argptr, format); vsprintf(string, format, argptr); va_end(argptr); debug_printf(string); } // 0x45404C static int scripts_tile_is_visible(int tile) { if (abs(tile_center_tile - tile) % 200 < 5) { return 1; } if (abs(tile_center_tile - tile) / 200 < 5) { return 1; } return 0; } // 0x45409C static int correctFidForRemovedItem(Object* a1, Object* a2, int flags) { if (a1 == obj_dude) { bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } int fid = a1->fid; int v8 = (fid & 0xF000) >> 12; int newFid = -1; if ((flags & 0x03000000) != 0) { if (a1 == obj_dude) { if (intface_is_item_right_hand()) { if ((flags & 0x02000000) != 0) { v8 = 0; } } else { if ((flags & 0x01000000) != 0) { v8 = 0; } } } else { if ((flags & 0x02000000) != 0) { v8 = 0; } } if (v8 == 0) { newFid = art_id(FID_TYPE(fid), fid & 0xFFF, FID_ANIM_TYPE(fid), 0, (fid & 0x70000000) >> 28); } } else { if (a1 == obj_dude) { newFid = art_id(FID_TYPE(fid), art_vault_guy_num, FID_ANIM_TYPE(fid), v8, (fid & 0x70000000) >> 28); } adjust_ac(a1, a2, NULL); } if (newFid != -1) { Rect rect; obj_change_fid(a1, newFid, &rect); tile_refresh_rect(&rect, map_elevation); } return 0; } // 0x4541C8 static void op_give_exp_points(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to give_exp_points", program->name); } if (stat_pc_add_experience(data) != 0) { int_debug("\nScript Error: %s: op_give_exp_points: stat_pc_set failed"); } } // 0x454238 static void op_scr_return(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to scr_return", program->name); } int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { script->field_28 = data; } } // 0x4542AC static void op_play_sfx(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("script error: %s: invalid arg to play_sfx", program->name); } char* name = interpretGetString(program, opcode, data); gsound_play_sfx_file(name); } // 0x454314 static void op_set_map_start(Program* program) { opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_map_start", program->name, arg); } } int x = data[3]; int y = data[2]; int elevation = data[1]; int rotation = data[0]; if (map_set_elevation(elevation) != 0) { int_debug("\nScript Error: %s: op_set_map_start: map_set_elevation failed", program->name); return; } int tile = 200 * y + x; if (tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW | TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) { int_debug("\nScript Error: %s: op_set_map_start: tile_set_center failed", program->name); return; } map_set_entrance_hex(tile, elevation, rotation); } // 0x4543F4 static void op_override_map_start(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to override_map_start", program->name, arg); } } int x = data[3]; int y = data[2]; int elevation = data[1]; int rotation = data[0]; char text[60]; sprintf(text, "OVERRIDE_MAP_START: x: %d, y: %d", x, y); debug_printf(text); int tile = 200 * y + x; int previousTile = tile_center_tile; if (tile != -1) { if (obj_set_rotation(obj_dude, rotation, NULL) != 0) { int_debug("\nError: %s: obj_set_rotation failed in override_map_start!", program->name); } if (obj_move_to_tile(obj_dude, tile, elevation, NULL) != 0) { int_debug("\nError: %s: obj_move_to_tile failed in override_map_start!", program->name); if (obj_move_to_tile(obj_dude, previousTile, elevation, NULL) != 0) { int_debug("\nError: %s: obj_move_to_tile RECOVERY Also failed!"); exit(1); } } tile_set_center(tile, TILE_SET_CENTER_REFRESH_WINDOW); tile_refresh_display(); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x454568 static void op_has_skill(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to has_skill", program->name, arg); } } Object* object = (Object*)data[1]; int skill = data[0]; int result = 0; if (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { result = skill_level(object, skill); } } else { dbg_error(program, "has_skill", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454634 static void op_using_skill(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to using_skill", program->name, arg); } } Object* object = (Object*)data[1]; int skill = data[0]; // NOTE: In the original source code this value is left uninitialized, that // explains why garbage is returned when using something else than dude and // SKILL_SNEAK as arguments. int result = 0; if (skill == SKILL_SNEAK && object == obj_dude) { result = is_pc_flag(DUDE_STATE_SNEAKING); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4546E8 static void op_roll_vs_skill(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to roll_vs_skill", program->name, arg); } } Object* object = (Object*)data[2]; int skill = data[1]; int modifier = data[0]; int roll = ROLL_CRITICAL_FAILURE; if (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { roll = skill_result(object, skill, modifier, &(script->howMuch)); } } } else { dbg_error(program, "roll_vs_skill", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, roll); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4547D4 static void op_skill_contest(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to skill_contest", program->name, arg); } } dbg_error(program, "skill_contest", SCRIPT_ERROR_NOT_IMPLEMENTED); interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454890 static void op_do_check(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to do_check", program->name, arg); } } Object* object = (Object*)data[2]; int stat = data[1]; int mod = data[0]; int roll = 0; if (object != NULL) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { switch (stat) { case STAT_STRENGTH: case STAT_PERCEPTION: case STAT_ENDURANCE: case STAT_CHARISMA: case STAT_INTELLIGENCE: case STAT_AGILITY: case STAT_LUCK: roll = stat_result(object, stat, mod, &(script->howMuch)); break; default: int_debug("\nScript Error: %s: op_do_check: Stat out of range", program->name); break; } } } else { dbg_error(program, "do_check", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, roll); interpretPushShort(program, VALUE_TYPE_INT); } // success // 0x4549A8 static void op_is_success(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to success", program->name); } int result = -1; switch (data) { case ROLL_CRITICAL_FAILURE: case ROLL_FAILURE: result = 0; break; case ROLL_SUCCESS: case ROLL_CRITICAL_SUCCESS: result = 1; break; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // critical // 0x454A44 static void op_is_critical(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to critical", program->name); } int result = -1; switch (data) { case ROLL_CRITICAL_FAILURE: case ROLL_CRITICAL_SUCCESS: result = 1; break; case ROLL_FAILURE: case ROLL_SUCCESS: result = 0; break; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454AD0 static void op_how_much(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to how_much", program->name); } int result = 0; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { result = script->howMuch; } else { dbg_error(program, "how_much", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454B6C static void op_mark_area_known(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to mark_area_known", program->name, arg); } } // TODO: Provide meaningful names. if (data[2] == 0) { if (data[0] == CITY_STATE_INVISIBLE) { wmAreaSetVisibleState(data[1], 0, 1); } else { wmAreaSetVisibleState(data[1], 1, 1); wmAreaMarkVisitedState(data[1], data[0]); } } else if (data[2] == 1) { wmMapMarkVisited(data[1]); } } // 0x454C34 static void op_reaction_influence(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reaction_influence", program->name, arg); } } int result = reaction_influence(); interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454CD4 static void op_random(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to random", program->name, arg); } } int result; if (vcr_status() == VCR_STATE_TURNED_OFF) { result = roll_random(data[1], data[0]); } else { result = (data[0] - data[1]) / 2; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454D88 static void op_roll_dice(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to roll_dice", program->name, arg); } } dbg_error(program, "roll_dice", SCRIPT_ERROR_NOT_IMPLEMENTED); interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454E28 static void op_move_to(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to move_to", program->name, arg); } } Object* object = (Object*)data[2]; int tile = data[1]; int elevation = data[0]; int newTile; if (object != NULL) { if (object == obj_dude) { bool tileLimitingEnabled = tile_get_scroll_limiting(); bool tileBlockingEnabled = tile_get_scroll_blocking(); if (tileLimitingEnabled) { tile_disable_scroll_limiting(); } if (tileBlockingEnabled) { tile_disable_scroll_blocking(); } Rect rect; newTile = obj_move_to_tile(object, tile, elevation, &rect); if (newTile != -1) { tile_set_center(object->tile, TILE_SET_CENTER_REFRESH_WINDOW); } if (tileLimitingEnabled) { tile_enable_scroll_limiting(); } if (tileBlockingEnabled) { tile_enable_scroll_blocking(); } } else { Rect before; obj_bound(object, &before); if (object->elevation != elevation && PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combat_delete_critter(object); } Rect after; newTile = obj_move_to_tile(object, tile, elevation, &after); if (newTile != -1) { rect_min_bound(&before, &after, &before); tile_refresh_rect(&before, map_elevation); } } } else { dbg_error(program, "move_to", SCRIPT_ERROR_OBJECT_IS_NULL); newTile = -1; } interpretPushLong(program, newTile); interpretPushShort(program, VALUE_TYPE_INT); } // 0x454FA8 static void op_create_object_sid(Program* program) { opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to create_object", program->name, arg); } } int pid = data[3]; int tile = data[2]; int elevation = data[1]; int sid = data[0]; Object* object = NULL; if (isLoadingGame() != 0) { debug_printf("\nError: attempt to Create critter in load/save-game: %s!", program->name); goto out; } if (pid == 0) { debug_printf("\nError: attempt to Create critter With PID of 0: %s!", program->name); goto out; } Proto* proto; if (proto_ptr(pid, &proto) != -1) { if (obj_new(&object, proto->fid, pid) != -1) { if (tile == -1) { tile = 0; } Rect rect; if (obj_move_to_tile(object, tile, elevation, &rect) != -1) { tile_refresh_rect(&rect, object->elevation); } } } if (sid != -1) { int scriptType = 0; switch (PID_TYPE(object->pid)) { case OBJ_TYPE_CRITTER: scriptType = SCRIPT_TYPE_CRITTER; break; case OBJ_TYPE_ITEM: case OBJ_TYPE_SCENERY: scriptType = SCRIPT_TYPE_ITEM; break; } if (object->sid != -1) { scr_remove(object->sid); object->sid = -1; } if (scr_new(&(object->sid), scriptType) == -1) { goto out; } Script* script; if (scr_ptr(object->sid, &script) == -1) { goto out; } script->field_14 = sid - 1; if (scriptType == SCRIPT_TYPE_SPATIAL) { script->sp.built_tile = builtTileCreate(object->tile, object->elevation); script->sp.radius = 3; } object->id = new_obj_id(); script->field_1C = object->id; script->owner = object; scr_find_str_run_info(sid - 1, &(script->field_50), object->sid); }; out: interpretPushLong(program, (int)object); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4551E4 static void op_destroy_object(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to destroy_object", program->name); } Object* object = (Object*)data; if (object == NULL) { dbg_error(program, "destroy_object", SCRIPT_ERROR_OBJECT_IS_NULL); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { if (isLoadingGame()) { debug_printf("\nError: attempt to destroy critter in load/save-game: %s!", program->name); program->flags &= ~PROGRAM_FLAG_0x20; return; } } bool isSelf = object == scr_find_obj_from_program(program); if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combat_delete_critter(object); } Object* owner = obj_top_environment(object); if (owner != NULL) { int quantity = item_count(owner, object); item_remove_mult(owner, object, quantity); if (owner == obj_dude) { bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } obj_connect(object, 1, 0, NULL); if (isSelf) { object->sid = -1; object->flags |= (OBJECT_HIDDEN | OBJECT_TEMPORARY); } else { register_clear(object); obj_erase_object(object, NULL); } } else { register_clear(object); Rect rect; obj_erase_object(object, &rect); tile_refresh_rect(&rect, map_elevation); } program->flags &= ~PROGRAM_FLAG_0x20; if (isSelf) { program->flags |= PROGRAM_FLAG_0x0100; } } // 0x455388 static void op_display_msg(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("script error: %s: invalid arg to display_msg", program->name); } char* string = interpretGetString(program, opcode, data); display_print(string); bool showScriptMessages = false; configGetBool(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages); if (showScriptMessages) { debug_printf("\n"); debug_printf(string); } } // 0x455430 static void op_script_overrides(Program* program) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { script->scriptOverrides = 1; } else { dbg_error(program, "script_overrides", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } } // 0x455470 static void op_obj_is_carrying_obj_pid(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to obj_is_carrying_obj", program->name, arg); } } Object* obj = (Object*)data[1]; int pid = data[0]; int result = 0; if (obj != NULL) { result = inven_pid_quantity_carried(obj, pid); } else { dbg_error(program, "obj_is_carrying_obj_pid", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455534 static void op_tile_contains_obj_pid(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to tile_contains_obj_pid", program->name, arg); } } int tile = data[2]; int elevation = data[1]; int pid = data[0]; int result = 0; Object* object = obj_find_first_at_tile(elevation, tile); while (object) { if (object->pid == pid) { result = 1; break; } object = obj_find_next_at_tile(); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455600 static void op_self_obj(Program* program) { Object* object = scr_find_obj_from_program(program); interpretPushLong(program, (int)object); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455624 static void op_source_obj(Program* program) { Object* object = NULL; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { object = script->source; } else { dbg_error(program, "source_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, (int)object); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455678 static void op_target_obj(Program* program) { Object* object = NULL; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { object = script->target; } else { dbg_error(program, "target_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, (int)object); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4556CC static void op_dude_obj(Program* program) { interpretPushLong(program, (int)obj_dude); interpretPushShort(program, VALUE_TYPE_INT); } // NOTE: The implementation is the same as in [op_target_obj]. // // 0x4556EC static void op_obj_being_used_with(Program* program) { Object* object = NULL; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { object = script->target; } else { dbg_error(program, "obj_being_used_with", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, (int)object); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455740 static void op_local_var(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { // FIXME: The error message is wrong. interpretError("script error: %s: invalid arg to op_global_var", program->name); } int value = -1; int sid = scr_find_sid_from_program(program); scr_get_local_var(sid, data, &value); interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4557C8 static void op_set_local_var(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_local_var", program->name, arg); } } int variable = data[1]; int value = data[0]; int sid = scr_find_sid_from_program(program); scr_set_local_var(sid, variable, value); } // 0x455858 static void op_map_var(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to op_map_var", program->name); } int value = map_get_global_var(data); interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4558C8 static void op_set_map_var(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_map_var", program->name, arg); } } int variable = data[1]; int value = data[0]; map_set_global_var(variable, value); } // 0x455950 static void op_global_var(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to op_global_var", program->name); } int value = -1; if (num_game_global_vars != 0) { value = game_get_global_var(data); } else { int_debug("\nScript Error: %s: op_global_var: no global vars found!", program->name); } interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4559EC static void op_set_global_var(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_global_var", program->name, arg); } } int variable = data[1]; int value = data[0]; if (num_game_global_vars != 0) { game_set_global_var(variable, value); } else { int_debug("\nScript Error: %s: op_set_global_var: no global vars found!", program->name); } } // 0x455A90 static void op_script_action(Program* program) { int action = 0; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { action = script->action; } else { dbg_error(program, "script_action", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, action); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455AE4 static void op_obj_type(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to op_obj_type", program->name); } Object* object = (Object*)data; int objectType = -1; if (object != NULL) { objectType = FID_TYPE(object->fid); } interpretPushLong(program, objectType); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455B6C static void op_obj_item_subtype(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to op_item_subtype", program->name); } Object* obj = (Object*)data; int itemType = -1; if (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_ITEM) { Proto* proto; if (proto_ptr(obj->pid, &proto) != -1) { itemType = item_get_type(obj); } } } interpretPushLong(program, itemType); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455C10 static void op_get_critter_stat(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to get_critter_stat", program->name, arg); } } Object* object = (Object*)data[1]; int stat = data[0]; int value = -1; if (object != NULL) { value = critterGetStat(object, stat); } else { dbg_error(program, "get_critter_stat", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // NOTE: Despite it's name it does not actually "set" stat, but "adjust". So // it's last argument is amount of adjustment, not it's final value. // // 0x455CCC static void op_set_critter_stat(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_critter_stat", program->name, arg); } } Object* object = (Object*)data[2]; int stat = data[1]; int value = data[0]; int result = 0; if (object != NULL) { if (object == obj_dude) { int currentValue = stat_get_base(object, stat); stat_set_base(object, stat, currentValue + value); } else { dbg_error(program, "set_critter_stat", SCRIPT_ERROR_FOLLOWS); debug_printf(" Can't modify anyone except obj_dude!"); result = -1; } } else { dbg_error(program, "set_critter_stat", SCRIPT_ERROR_OBJECT_IS_NULL); result = -1; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x455DC8 static void op_animate_stand_obj(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to animate_stand_obj", program->name); } Object* object = (Object*)data; if (object == NULL) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { dbg_error(program, "animate_stand_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } object = scr_find_obj_from_program(program); } if (!isInCombat()) { register_begin(ANIMATION_REQUEST_UNRESERVED); register_object_animate(object, ANIM_STAND, 0); register_end(); } } // 0x455E7C static void op_animate_stand_reverse_obj(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { // FIXME: typo in message, should be animate_stand_reverse_obj. interpretError("script error: %s: invalid arg to animate_stand_obj", program->name); } Object* object = (Object*)data; if (object == NULL) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { dbg_error(program, "animate_stand_reverse_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } object = scr_find_obj_from_program(program); } if (!isInCombat()) { register_begin(ANIMATION_REQUEST_UNRESERVED); register_object_animate_reverse(object, ANIM_STAND, 0); register_end(); } } // 0x455F30 static void op_animate_move_obj_to_tile(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to animate_move_obj_to_tile", program->name, arg); } } Object* object = (Object*)data[2]; int tile = data[1]; int flags = data[0]; if (object == NULL) { dbg_error(program, "animate_move_obj_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (tile <= -1) { return; } int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { dbg_error(program, "animate_move_obj_to_tile", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } if (!critter_is_active(object)) { return; } if (isInCombat()) { return; } if ((flags & 0x10) != 0) { register_clear(object); flags &= ~0x10; } register_begin(ANIMATION_REQUEST_UNRESERVED); if (flags == 0) { register_object_move_to_tile(object, tile, object->elevation, -1, 0); } else { register_object_run_to_tile(object, tile, object->elevation, -1, 0); } register_end(); } // 0x45607C static void op_tile_in_tile_rect(Program* program) { opcode_t opcode[5]; int data[5]; Point points[5]; for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to tile_in_tile_rect", program->name, arg); } points[arg].x = data[arg] % 200; points[arg].y = data[arg] / 200; } int x = points[0].x; int y = points[0].y; int minX = points[1].x; int maxX = points[4].x; int minY = points[4].y; int maxY = points[1].y; int result = 0; if (x >= minX && x <= maxX && y >= minY && y <= maxY) { result = 1; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x456170 static void op_make_daytime(Program* program) { } // 0x456174 static void op_tile_distance(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to tile_distance", program->name, arg); } } int tile1 = data[1]; int tile2 = data[0]; int distance; if (tile1 != -1 && tile2 != -1) { distance = tile_dist(tile1, tile2); } else { distance = 9999; } interpretPushLong(program, distance); interpretPushShort(program, VALUE_TYPE_INT); } // 0x456228 static void op_tile_distance_objs(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to tile_distance_objs", program->name, arg); } } Object* object1 = (Object*)data[1]; Object* object2 = (Object*)data[0]; int distance = 9999; if (object1 != NULL && object2 != NULL) { if ((unsigned int)data[1] >= HEX_GRID_SIZE && (unsigned int)data[0] >= HEX_GRID_SIZE) { if (object1->elevation == object2->elevation) { if (object1->tile != -1 && object2->tile != -1) { distance = tile_dist(object1->tile, object2->tile); } } } else { dbg_error(program, "tile_distance_objs", SCRIPT_ERROR_FOLLOWS); debug_printf(" Passed a tile # instead of an object!!!BADBADBAD!"); } } interpretPushLong(program, distance); interpretPushShort(program, VALUE_TYPE_INT); } // 0x456324 static void op_tile_num(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to tile_num", program->name); } Object* obj = (Object*)data; int tile = -1; if (obj != NULL) { tile = obj->tile; } else { dbg_error(program, "tile_num", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, tile); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4563B4 static void op_tile_num_in_direction(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to tile_num_in_direction", program->name, arg); } } int origin = data[2]; int rotation = data[1]; int distance = data[0]; int tile = -1; if (origin != -1) { if (rotation < ROTATION_COUNT) { if (distance != 0) { tile = tile_num_in_direction(origin, rotation, distance); if (tile < -1) { debug_printf("\nError: %s: op_tile_num_in_direction got #: %d", program->name, tile); tile = -1; } } } else { dbg_error(program, "tile_num_in_direction", SCRIPT_ERROR_FOLLOWS); debug_printf(" rotation out of Range!"); } } else { dbg_error(program, "tile_num_in_direction", SCRIPT_ERROR_FOLLOWS); debug_printf(" tileNum is -1!"); } interpretPushLong(program, tile); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4564D4 static void op_pickup_obj(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to pickup_obj", program->name); } Object* object = (Object*)data; if (object == NULL) { return; } int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == 1) { dbg_error(program, "pickup_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } if (script->target == NULL) { dbg_error(program, "pickup_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } action_get_an_object(script->target, object); } // 0x456580 static void op_drop_obj(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to drop_obj", program->name); } Object* object = (Object*)data; if (object == NULL) { return; } int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. dbg_error(program, "drop_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (script->target == NULL) { // FIXME: Should be SCRIPT_ERROR_OBJECT_IS_NULL. dbg_error(program, "drop_obj", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); return; } obj_drop(script->target, object); } // 0x45662C static void op_add_obj_to_inven(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to add_obj_to_inven", program->name, arg); } } Object* owner = (Object*)data[1]; Object* item = (Object*)data[0]; if (owner == NULL || item == NULL) { return; } if (item->owner == NULL) { if (item_add_force(owner, item, 1) == 0) { Rect rect; obj_disconnect(item, &rect); tile_refresh_rect(&rect, item->elevation); } } else { dbg_error(program, "add_obj_to_inven", SCRIPT_ERROR_FOLLOWS); debug_printf(" Item was already attached to something else!"); } } // 0x456708 static void op_rm_obj_from_inven(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to rm_obj_from_inven", program->name, arg); } } Object* owner = (Object*)data[1]; Object* item = (Object*)data[0]; if (owner == NULL || item == NULL) { return; } bool updateFlags = false; int flags = 0; if ((item->flags & OBJECT_EQUIPPED) != 0) { if ((item->flags & OBJECT_IN_LEFT_HAND) != 0) { flags |= OBJECT_IN_LEFT_HAND; } if ((item->flags & OBJECT_IN_RIGHT_HAND) != 0) { flags |= OBJECT_IN_RIGHT_HAND; } if ((item->flags & OBJECT_WORN) != 0) { flags |= OBJECT_WORN; } updateFlags = true; } if (item_remove_mult(owner, item, 1) == 0) { Rect rect; obj_connect(item, 1, 0, &rect); tile_refresh_rect(&rect, item->elevation); if (updateFlags) { correctFidForRemovedItem(owner, item, flags); } } } // 0x45681C static void op_wield_obj_critter(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to wield_obj_critter", program->name, arg); } } Object* critter = (Object*)data[1]; Object* item = (Object*)data[0]; if (critter == NULL) { dbg_error(program, "wield_obj_critter", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (item == NULL) { dbg_error(program, "wield_obj_critter", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (PID_TYPE(critter->pid) != OBJ_TYPE_CRITTER) { dbg_error(program, "wield_obj_critter", SCRIPT_ERROR_FOLLOWS); debug_printf(" Only works for critters! ERROR ERROR ERROR!"); return; } int hand = HAND_RIGHT; bool shouldAdjustArmorClass = false; Object* oldArmor = NULL; Object* newArmor = NULL; if (critter == obj_dude) { if (intface_is_item_right_hand() == HAND_LEFT) { hand = HAND_LEFT; } if (item_get_type(item) == ITEM_TYPE_ARMOR) { oldArmor = inven_worn(obj_dude); } shouldAdjustArmorClass = true; newArmor = item; } if (inven_wield(critter, item, hand) == -1) { dbg_error(program, "wield_obj_critter", SCRIPT_ERROR_FOLLOWS); debug_printf(" inven_wield failed! ERROR ERROR ERROR!"); return; } if (critter == obj_dude) { if (shouldAdjustArmorClass) { adjust_ac(critter, oldArmor, newArmor); } bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } // 0x4569D0 static void op_use_obj(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to use_obj", program->name); } Object* object = (Object*)data; if (object == NULL) { dbg_error(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. dbg_error(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (script->target == NULL) { dbg_error(program, "use_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Object* self = scr_find_obj_from_program(program); if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { action_use_an_object(script->target, object); } else { obj_use(self, object); } } // 0x456AC4 static void op_obj_can_see_obj(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to obj_can_see_obj", program->name, arg); } } Object* object1 = (Object*)data[1]; Object* object2 = (Object*)data[0]; int result = 0; if (object1 != NULL && object2 != NULL) { if (object2->tile != -1) { // NOTE: Looks like dead code, I guess these checks were incorporated // into higher level functions, but this code left intact. if (object2 == obj_dude) { is_pc_flag(0); } critterGetStat(object1, STAT_PERCEPTION); if (is_within_perception(object1, object2)) { Object* a5; make_straight_path(object1, object1->tile, object2->tile, NULL, &a5, 16); if (a5 == object2) { result = 1; } } } } else { dbg_error(program, "obj_can_see_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x456C00 static void op_attack(Program* program) { opcode_t opcode[8]; int data[8]; for (int arg = 0; arg < 8; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to attack", program->name, arg); } } Object* target = (Object*)data[7]; if (target == NULL) { dbg_error(program, "attack", SCRIPT_ERROR_OBJECT_IS_NULL); return; } program->flags |= PROGRAM_FLAG_0x20; Object* self = scr_find_obj_from_program(program); if (self == NULL) { program->flags &= ~PROGRAM_FLAG_0x20; return; } if (!critter_is_active(self) || (self->flags & OBJECT_HIDDEN) != 0) { debug_printf("\n But is already Inactive (Dead/Stunned/Invisible)"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (!critter_is_active(target) || (target->flags & OBJECT_HIDDEN) != 0) { debug_printf("\n But target is already dead or invisible"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if ((target->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { debug_printf("\n But target is AFRAID"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (gdialogActive()) { // TODO: Might be an error, program flag is not removed. return; } if (isInCombat()) { CritterCombatData* combatData = &(self->data.critter.combat); if ((combatData->maneuver & CRITTER_MANEUVER_0x01) == 0) { combatData->maneuver |= CRITTER_MANEUVER_0x01; combatData->whoHitMe = target; } } else { STRUCT_664980 attack; attack.attacker = self; attack.defender = target; attack.actionPointsBonus = 0; attack.accuracyBonus = data[4]; attack.damageBonus = 0; attack.minDamage = data[3]; attack.maxDamage = data[2]; // TODO: Something is probably broken here, why it wants // flags to be the same? Maybe because both of them // are applied to defender because of the bug in 0x422F3C? if (data[1] == data[0]) { attack.field_1C = 1; attack.field_24 = data[0]; attack.field_20 = data[1]; } else { attack.field_1C = 0; } scripts_request_combat(&attack); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x456DF0 static void op_start_gdialog(Program* program) { opcode_t opcode[5]; int data[5]; for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to start_gdialog", program->name, arg); } } Object* obj = (Object*)data[3]; int reactionLevel = data[2]; int headId = data[1]; int backgroundId = data[0]; if (isInCombat()) { return; } if (obj == NULL) { dbg_error(program, "start_gdialog", SCRIPT_ERROR_OBJECT_IS_NULL); return; } dialogue_head = -1; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { Proto* proto; if (proto_ptr(obj->pid, &proto) == -1) { return; } } if (headId != -1) { dialogue_head = art_id(OBJ_TYPE_HEAD, headId, 0, 0, 0); } gdialogSetBackground(backgroundId); dialogue_mood = reactionLevel; if (dialogue_head != -1) { int npcReactionValue = reaction_get(dialog_target); int npcReactionType = reaction_lookup_internal(npcReactionValue); switch (npcReactionType) { case NPC_REACTION_BAD: dialogue_mood = FIDGET_BAD; break; case NPC_REACTION_NEUTRAL: dialogue_mood = FIDGET_NEUTRAL; break; case NPC_REACTION_GOOD: dialogue_mood = FIDGET_GOOD; break; } } dialogue_scr_id = scr_find_sid_from_program(program); dialog_target = scr_find_obj_from_program(program); gdialogInitFromScript(dialogue_head, dialogue_mood); } // 0x456F80 static void op_end_dialogue(Program* program) { if (gdialogExitFromScript() != -1) { dialog_target = NULL; dialogue_scr_id = -1; } } // 0x456FA4 static void op_dialogue_reaction(Program* program) { opcode_t opcode = interpretPopShort(program); int value = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, value); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to dialogue_reaction", program->name); } dialogue_mood = value; talk_to_critter_reacts(value); } // 0x457110 static void op_metarule3(Program* program) { opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to metarule3", program->name, arg); } } int rule = data[3]; int result = 0; switch (rule) { case METARULE3_CLR_FIXED_TIMED_EVENTS: if (1) { scrSetQueueTestVals((Object*)data[2], data[1]); queue_clear_type(EVENT_TYPE_SCRIPT, scrQueueRemoveFixed); } break; case METARULE3_MARK_SUBTILE: result = wmSubTileMarkRadiusVisited(data[2], data[1], data[0]); break; case METARULE3_GET_KILL_COUNT: result = critter_kill_count(data[2]); break; case METARULE3_MARK_MAP_ENTRANCE: result = wmMapMarkMapEntranceState(data[2], data[1], data[0]); break; case METARULE3_WM_SUBTILE_STATE: if (1) { int state; if (wmSubTileGetVisitedState(data[2], data[1], &state) == 0) { result = state; } } break; case METARULE3_TILE_GET_NEXT_CRITTER: if (1) { int tile = data[2]; int elevation = data[1]; Object* previousCritter = (Object*)data[0]; bool critterFound = previousCritter == NULL; Object* object = obj_find_first_at_tile(elevation, tile); while (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { if (critterFound) { result = (int)object; break; } } if (object == previousCritter) { critterFound = true; } object = obj_find_next_at_tile(); } } break; case METARULE3_ART_SET_BASE_FID_NUM: if (1) { Object* obj = (Object*)data[2]; int frmId = data[1]; int fid = art_id(FID_TYPE(obj->fid), frmId, FID_ANIM_TYPE(obj->fid), (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 28); Rect updatedRect; obj_change_fid(obj, fid, &updatedRect); tile_refresh_rect(&updatedRect, map_elevation); } break; case METARULE3_TILE_SET_CENTER: result = tile_set_center(data[2], TILE_SET_CENTER_REFRESH_WINDOW); break; case METARULE3_109: result = ai_get_chem_use_value((Object*)data[2]); break; case METARULE3_110: result = wmCarIsOutOfGas() ? 1 : 0; break; case METARULE3_111: result = map_target_load_area(); break; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45734C static void op_set_map_music(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { // FIXME: argument is wrong, should be 1. interpretError("script error: %s: invalid arg %d to set_map_music", program->name, 2); } int mapIndex = data[1]; char* string = NULL; if ((opcode[0] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode[0], data[0]); } else { // FIXME: argument is wrong, should be 0. interpretError("script error: %s: invalid arg %d to set_map_music", program->name, 2); } debug_printf("\nset_map_music: %d, %s", mapIndex, string); wmSetMapMusic(mapIndex, string); } // NOTE: Function name is a bit misleading. Last parameter is a boolean value // where 1 or true makes object invisible, and value 0 (false) makes it visible // again. So a better name for this function is opSetObjectInvisible. // // // 0x45741C static void op_set_obj_visibility(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_obj_visibility", program->name, arg); } } Object* obj = (Object*)data[1]; int invisible = data[0]; if (obj == NULL) { dbg_error(program, "set_obj_visibility", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (isLoadingGame()) { debug_printf("Error: attempt to set_obj_visibility in load/save-game: %s!", program->name); return; } if (invisible != 0) { if ((obj->flags & OBJECT_HIDDEN) == 0) { if (isInCombat()) { obj_turn_off_outline(obj, NULL); obj_remove_outline(obj, NULL); } Rect rect; if (obj_turn_off(obj, &rect) != -1) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { obj->flags |= OBJECT_NO_BLOCK; } tile_refresh_rect(&rect, obj->elevation); } } } else { if ((obj->flags & OBJECT_HIDDEN) != 0) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { obj->flags &= ~OBJECT_NO_BLOCK; } Rect rect; if (obj_turn_on(obj, &rect) != -1) { tile_refresh_rect(&rect, obj->elevation); } } } } // 0x45755C static void op_load_map(Program* program) { opcode_t opcode[2]; int data[2]; opcode[0] = interpretPopShort(program); data[0] = interpretPopLong(program); if (opcode[0] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[0], data[0]); } if ((opcode[0] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg 0 to load_map", program->name); } opcode[1] = interpretPopShort(program); data[1] = interpretPopLong(program); if (opcode[1] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[1], data[1]); } int param = data[0]; int mapIndexOrName = data[1]; char* mapName = NULL; if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { mapName = interpretGetString(program, opcode[1], mapIndexOrName); } else { interpretError("script error: %s: invalid arg 1 to load_map", program->name); } } int mapIndex = -1; if (mapName != NULL) { game_global_vars[GVAR_LOAD_MAP_INDEX] = param; mapIndex = wmMapMatchNameToIdx(mapName); } else { if (mapIndexOrName >= 0) { game_global_vars[GVAR_LOAD_MAP_INDEX] = param; mapIndex = mapIndexOrName; } } if (mapIndex != -1) { MapTransition transition; transition.map = mapIndex; transition.elevation = -1; transition.tile = -1; transition.rotation = -1; map_leave_map(&transition); } } // 0x457680 static void op_wm_area_set_pos(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to wm_area_set_pos", program->name, arg); } } int city = data[2]; int x = data[1]; int y = data[0]; if (wmAreaSetWorldPos(city, x, y) == -1) { dbg_error(program, "wm_area_set_pos", SCRIPT_ERROR_FOLLOWS); debug_printf("Invalid Parameter!"); } } // 0x457730 static void op_set_exit_grids(Program* program) { opcode_t opcode[5]; int data[5]; for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to set_exit_grids", program->name, arg); } } int elevation = data[4]; int destinationMap = data[3]; int destinationElevation = data[2]; int destinationTile = data[1]; int destinationRotation = data[0]; Object* object = obj_find_first_at(elevation); while (object != NULL) { if (object->pid >= PROTO_ID_0x5000010 && object->pid <= PROTO_ID_0x5000017) { object->data.misc.map = destinationMap; object->data.misc.tile = destinationTile; object->data.misc.elevation = destinationElevation; } object = obj_find_next_at(); } } // 0x4577EC static void op_anim_busy(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to anim_busy", program->name); } Object* object = (Object*)data; int rc = 0; if (object != NULL) { rc = anim_busy(object); } else { dbg_error(program, "anim_busy", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, rc); interpretPushShort(program, VALUE_TYPE_INT); } // 0x457880 static void op_critter_heal(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_heal", program->name, arg); } } Object* critter = (Object*)data[1]; int amount = data[0]; int rc = critter_adjust_hits(critter, amount); if (critter == obj_dude) { intface_update_hit_points(true); } interpretPushLong(program, rc); interpretPushShort(program, VALUE_TYPE_INT); } // 0x457934 static void op_set_light_level(Program* program) { // Maps light level to light intensity. // // Middle value is mapped one-to-one which corresponds to 50% light level // (cavern lighting). Light levels above (51-100%) and below (0-49) is // calculated as percentage from two adjacent light values. // // 0x453F90 static const int dword_453F90[3] = { 0x4000, 0xA000, 0x10000, }; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to set_light_level", program->name); } int lightLevel = data; if (data == 50) { light_set_ambient(dword_453F90[1], true); return; } int lightIntensity; if (data > 50) { lightIntensity = dword_453F90[1] + data * (dword_453F90[2] - dword_453F90[1]) / 100; } else { lightIntensity = dword_453F90[0] + data * (dword_453F90[1] - dword_453F90[0]) / 100; } light_set_ambient(lightIntensity, true); } // 0x4579F4 static void op_game_time(Program* program) { int time = game_time(); interpretPushLong(program, time); interpretPushShort(program, VALUE_TYPE_INT); } // 0x457A18 static void op_game_time_in_seconds(Program* program) { int time = game_time(); interpretPushLong(program, time / 10); interpretPushShort(program, VALUE_TYPE_INT); } // 0x457A44 static void op_elevation(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to elevation", program->name); } Object* object = (Object*)data; int elevation = 0; if (object != NULL) { elevation = object->elevation; } else { dbg_error(program, "elevation", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, elevation); interpretPushShort(program, VALUE_TYPE_INT); } // 0x457AD4 static void op_kill_critter(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to kill_critter", program->name, arg); } } Object* object = (Object*)data[1]; int deathFrame = data[0]; if (object == NULL) { dbg_error(program, "kill_critter", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (isLoadingGame()) { debug_printf("\nError: attempt to destroy critter in load/save-game: %s!", program->name); } program->flags |= PROGRAM_FLAG_0x20; Object* self = scr_find_obj_from_program(program); bool isSelf = self == object; register_clear(object); combat_delete_critter(object); critter_kill(object, deathFrame, 1); program->flags &= ~PROGRAM_FLAG_0x20; if (isSelf) { program->flags |= PROGRAM_FLAG_0x0100; } } // [forceBack] is to force fall back animation, otherwise it's fall front if it's present int correctDeath(Object* critter, int anim, bool forceBack) { if (anim >= ANIM_BIG_HOLE_SF && anim <= ANIM_FALL_FRONT_BLOOD_SF) { int violenceLevel = VIOLENCE_LEVEL_MAXIMUM_BLOOD; config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel); bool useStandardDeath = false; if (violenceLevel < VIOLENCE_LEVEL_MAXIMUM_BLOOD) { useStandardDeath = true; } else { int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, anim, (critter->fid & 0xF000) >> 12, critter->rotation + 1); if (!art_exists(fid)) { useStandardDeath = true; } } if (useStandardDeath) { if (forceBack) { anim = ANIM_FALL_BACK; } else { int fid = art_id(OBJ_TYPE_CRITTER, critter->fid & 0xFFF, ANIM_FALL_FRONT, (critter->fid & 0xF000) >> 12, critter->rotation + 1); if (art_exists(fid)) { anim = ANIM_FALL_FRONT; } else { anim = ANIM_FALL_BACK; } } } } return anim; } // 0x457CB4 static void op_kill_critter_type(Program* program) { // 0x518ED0 static int ftList[11] = { ANIM_FALL_BACK_BLOOD_SF, ANIM_BIG_HOLE_SF, ANIM_CHARRED_BODY_SF, ANIM_CHUNKS_OF_FLESH_SF, ANIM_FALL_FRONT_BLOOD_SF, ANIM_FALL_BACK_BLOOD_SF, ANIM_DANCING_AUTOFIRE_SF, ANIM_SLICED_IN_HALF_SF, ANIM_EXPLODED_TO_NOTHING_SF, ANIM_FALL_BACK_BLOOD_SF, ANIM_FALL_FRONT_BLOOD_SF, }; opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to kill_critter", program->name, arg); } } int pid = data[1]; int deathFrame = data[0]; if (isLoadingGame()) { debug_printf("\nError: attempt to destroy critter in load/save-game: %s!", program->name); return; } program->flags |= PROGRAM_FLAG_0x20; Object* previousObj = NULL; int count = 0; int v3 = 0; Object* obj = obj_find_first(); while (obj != NULL) { if (FID_ANIM_TYPE(obj->fid) >= ANIM_FALL_BACK_SF) { obj = obj_find_next(); continue; } if ((obj->flags & OBJECT_HIDDEN) == 0 && obj->pid == pid && !critter_is_dead(obj)) { if (obj == previousObj || count > 200) { dbg_error(program, "kill_critter_type", SCRIPT_ERROR_FOLLOWS); debug_printf(" Infinite loop destroying critters!"); program->flags &= ~PROGRAM_FLAG_0x20; return; } register_clear(obj); if (deathFrame != 0) { combat_delete_critter(obj); if (deathFrame == 1) { int anim = correctDeath(obj, ftList[v3], 1); critter_kill(obj, anim, 1); v3 += 1; if (v3 >= 11) { v3 = 0; } } else { critter_kill(obj, ANIM_FALL_BACK_SF, 1); } } else { register_clear(obj); Rect rect; obj_erase_object(obj, &rect); tile_refresh_rect(&rect, map_elevation); } previousObj = obj; count += 1; obj_find_first(); map_data.lastVisitTime = game_time(); } obj = obj_find_next(); } program->flags &= ~PROGRAM_FLAG_0x20; } // critter_dmg // 0x457EB4 static void op_critter_damage(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_damage", program->name, arg); } } Object* object = (Object*)data[2]; int amount = data[1]; int damageTypeWithFlags = data[0]; if (object == NULL) { dbg_error(program, "critter_damage", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (PID_TYPE(object->pid) != OBJ_TYPE_CRITTER) { dbg_error(program, "critter_damage", SCRIPT_ERROR_FOLLOWS); debug_printf(" Can't call on non-critters!"); return; } Object* self = scr_find_obj_from_program(program); if (object->data.critter.combat.whoHitMeCid == -1) { object->data.critter.combat.whoHitMe = NULL; } bool animate = (damageTypeWithFlags & 0x200) == 0; bool bypassArmor = (damageTypeWithFlags & 0x100) != 0; int damageType = damageTypeWithFlags & ~(0x100 | 0x200); action_dmg(object->tile, object->elevation, amount, amount, damageType, animate, bypassArmor); program->flags &= ~PROGRAM_FLAG_0x20; if (self == object) { program->flags |= PROGRAM_FLAG_0x0100; } } // 0x457FF0 static void op_add_timer_event(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to add_timer_event", program->name, arg); } } Object* object = (Object*)data[2]; int delay = data[1]; int param = data[0]; if (object == NULL) { int_debug("\nScript Error: %s: op_add_timer_event: pobj is NULL!", program->name); return; } script_q_add(object->sid, delay, param); } // 0x458094 static void op_rm_timer_event(Program* program) { int elevation; elevation = 0; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to rm_timer_event", program->name); } Object* object = (Object*)data; if (object == NULL) { // FIXME: Should be op_rm_timer_event. int_debug("\nScript Error: %s: op_add_timer_event: pobj is NULL!"); return; } queue_remove(object); } // Converts seconds into game ticks. // // 0x458108 static void op_game_ticks(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to game_ticks", program->name); } int ticks = data; if (ticks < 0) { ticks = 0; } interpretPushLong(program, ticks * 10); interpretPushShort(program, VALUE_TYPE_INT); } // NOTE: The name of this function is misleading. It has (almost) nothing to do // with player's "Traits" as a feature. Instead it's used to query many // information of the critters using passed parameters. It's like "metarule" but // for critters. // // 0x458180 // has_trait static void op_has_trait(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to has_trait", program->name, arg); } } int type = data[2]; Object* object = (Object*)data[1]; int param = data[0]; int result = 0; if (object != NULL) { switch (type) { case CRITTER_TRAIT_PERK: if (param < PERK_COUNT) { result = perk_level(object, param); } else { int_debug("\nScript Error: %s: op_has_trait: Perk out of range", program->name); } break; case CRITTER_TRAIT_OBJECT: switch (param) { case CRITTER_TRAIT_OBJECT_AI_PACKET: if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { result = object->data.critter.combat.aiPacket; } break; case CRITTER_TRAIT_OBJECT_TEAM: if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { result = object->data.critter.combat.team; } break; case CRITTER_TRAIT_OBJECT_ROTATION: result = object->rotation; break; case CRITTER_TRAIT_OBJECT_IS_INVISIBLE: result = (object->flags & OBJECT_HIDDEN) == 0; break; case CRITTER_TRAIT_OBJECT_GET_INVENTORY_WEIGHT: result = item_total_weight(object); break; } break; case CRITTER_TRAIT_TRAIT: if (param < TRAIT_COUNT) { result = trait_level(param); } else { int_debug("\nScript Error: %s: op_has_trait: Trait out of range", program->name); } break; default: int_debug("\nScript Error: %s: op_has_trait: Trait out of range", program->name); break; } } else { dbg_error(program, "has_trait", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45835C static void op_obj_can_hear_obj(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d, to obj_can_hear_obj", program->name, arg); } } Object* object1 = (Object*)data[1]; Object* object2 = (Object*)data[0]; bool canHear = false; // FIXME: This is clearly an error. If any of the object is NULL // dereferencing will crash the game. if (object2 == NULL || object1 == NULL) { if (object2->elevation == object1->elevation) { if (object2->tile != -1 && object1->tile != -1) { if (is_within_perception(object2, object1)) { canHear = true; } } } } interpretPushLong(program, canHear); interpretPushShort(program, VALUE_TYPE_INT); } // 0x458438 static void op_game_time_hour(Program* program) { int value = game_time_hour(); interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45845C static void op_fixed_param(Program* program) { int fixedParam = 0; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { fixedParam = script->fixedParam; } else { dbg_error(program, "fixed_param", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, fixedParam); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4584B0 static void op_tile_is_visible(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to tile_is_visible", program->name); } int isVisible = 0; if (scripts_tile_is_visible(data)) { isVisible = 1; } interpretPushLong(program, isVisible); interpretPushShort(program, VALUE_TYPE_INT); } // 0x458534 static void op_dialogue_system_enter(Program* program) { int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) == -1) { return; } Object* self = scr_find_obj_from_program(program); if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { if (!critter_is_active(self)) { return; } } if (isInCombat()) { return; } if (game_state_request(GAME_STATE_4) == -1) { return; } dialog_target = scr_find_obj_from_program(program); } // 0x458594 static void op_action_being_used(Program* program) { int action = -1; int sid = scr_find_sid_from_program(program); Script* script; if (scr_ptr(sid, &script) != -1) { action = script->actionBeingUsed; } else { dbg_error(program, "action_being_used", SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID); } interpretPushLong(program, action); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4585E8 static void op_critter_state(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to critter_state", program->name); } Object* critter = (Object*)data; int state = CRITTER_STATE_DEAD; if (critter != NULL && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { if (critter_is_active(critter)) { state = CRITTER_STATE_NORMAL; int anim = FID_ANIM_TYPE(critter->fid); if (anim >= ANIM_FALL_BACK_SF && anim <= ANIM_FALL_FRONT_SF) { state = CRITTER_STATE_PRONE; } state |= (critter->data.critter.combat.results & DAM_CRIP); } else { if (!critter_is_dead(critter)) { state = CRITTER_STATE_PRONE; } } } else { dbg_error(program, "critter_state", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, state); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4586C8 static void op_game_time_advance(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to game_time_advance", program->name); } int days = data / GAME_TIME_TICKS_PER_DAY; int remainder = data % GAME_TIME_TICKS_PER_DAY; for (int day = 0; day < days; day++) { inc_game_time(GAME_TIME_TICKS_PER_DAY); queue_process(); } inc_game_time(remainder); queue_process(); } // 0x458760 static void op_radiation_inc(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to radiation_inc", program->name, arg); } } Object* object = (Object*)data[1]; int amount = data[0]; if (object == NULL) { dbg_error(program, "radiation_inc", SCRIPT_ERROR_OBJECT_IS_NULL); return; } critter_adjust_rads(object, amount); } // 0x458800 static void op_radiation_dec(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to radiation_dec", program->name, arg); } } Object* object = (Object*)data[1]; int amount = data[0]; if (object == NULL) { dbg_error(program, "radiation_dec", SCRIPT_ERROR_OBJECT_IS_NULL); return; } int radiation = critter_get_rads(object); int adjustment = radiation >= 0 ? -amount : 0; critter_adjust_rads(object, adjustment); } // 0x4588B4 static void op_critter_attempt_placement(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_attempt_placement", program->name, arg); } } Object* critter = (Object*)data[2]; int tile = data[1]; int elevation = data[0]; if (critter == NULL) { dbg_error(program, "critter_attempt_placement", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (elevation != critter->elevation && PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { combat_delete_critter(critter); } obj_move_to_tile(critter, 0, elevation, NULL); int rc = obj_attempt_placement(critter, tile, elevation, 1); interpretPushLong(program, rc); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4589A0 static void op_obj_pid(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_pid", program->name); } Object* obj = (Object*)data; int pid = -1; if (obj) { pid = obj->pid; } else { dbg_error(program, "obj_pid", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, pid); interpretPushShort(program, VALUE_TYPE_INT); } // 0x458A30 static void op_cur_map_index(Program* program) { int mapIndex = map_get_index_number(); interpretPushLong(program, mapIndex); interpretPushShort(program, VALUE_TYPE_INT); } // 0x458A54 static void op_critter_add_trait(Program* program) { opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_add_trait", program->name, arg); } } Object* object = (Object*)data[3]; int kind = data[2]; int param = data[1]; int value = data[0]; if (object != NULL) { if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { switch (kind) { case CRITTER_TRAIT_PERK: if (1) { char* critterName = critter_name(object); char* perkName = perk_name(param); debug_printf("\nintextra::critter_add_trait: Adding Perk %s to %s", perkName, critterName); if (value > 0) { if (perk_add_force(object, param) != 0) { int_debug("\nScript Error: %s: op_critter_add_trait: perk_add_force failed", program->name); debug_printf("Perk: %d", param); } } else { if (perk_sub(object, param) != 0) { // FIXME: typo in debug message, should be perk_sub int_debug("\nScript Error: %s: op_critter_add_trait: per_sub failed", program->name); debug_printf("Perk: %d", param); } } if (object == obj_dude) { intface_update_hit_points(true); } } break; case CRITTER_TRAIT_OBJECT: switch (param) { case CRITTER_TRAIT_OBJECT_AI_PACKET: combat_ai_set_ai_packet(object, value); break; case CRITTER_TRAIT_OBJECT_TEAM: if (isPartyMember(object)) { break; } if (object->data.critter.combat.team == value) { break; } if (isLoadingGame()) { break; } combatai_switch_team(object, value); break; } break; default: int_debug("\nScript Error: %s: op_critter_add_trait: Trait out of range", program->name); break; } } } else { dbg_error(program, "critter_add_trait", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, -1); interpretPushShort(program, VALUE_TYPE_INT); } // 0x458C2C static void op_critter_rm_trait(Program* program) { opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_rm_trait", program->name, arg); } } Object* object = (Object*)data[3]; int kind = data[2]; int param = data[1]; int value = data[0]; if (object == NULL) { dbg_error(program, "critter_rm_trait", SCRIPT_ERROR_OBJECT_IS_NULL); // FIXME: Ruins stack. return; } if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { switch (kind) { case CRITTER_TRAIT_PERK: while (perk_level(object, param) > 0) { if (perk_sub(object, param) != 0) { int_debug("\nScript Error: op_critter_rm_trait: perk_sub failed"); } } break; default: int_debug("\nScript Error: %s: op_critter_rm_trait: Trait out of range", program->name); break; } } interpretPushLong(program, -1); interpretPushShort(program, VALUE_TYPE_INT); } // 0x458D38 static void op_proto_data(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to proto_data", program->name, arg); } } int pid = data[1]; int member = data[0]; ProtoDataMemberValue value; value.integerValue = 0; int valueType = proto_data_member(pid, member, &value); switch (valueType) { case PROTO_DATA_MEMBER_TYPE_INT: interpretPushLong(program, value.integerValue); interpretPushShort(program, VALUE_TYPE_INT); break; case PROTO_DATA_MEMBER_TYPE_STRING: interpretPushLong(program, interpretAddString(program, value.stringValue)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); break; default: interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); break; } } // 0x458E10 static void op_message_str(Program* program) { // 0x518EFC static char errStr[] = "Error"; opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to message_str", program->name, arg); } } int messageListIndex = data[1]; int messageIndex = data[0]; char* string; if (messageIndex >= 1) { string = scr_get_msg_str_speech(messageListIndex, messageIndex, 1); if (string == NULL) { debug_printf("\nError: No message file EXISTS!: index %d, line %d", messageListIndex, messageIndex); string = errStr; } } else { string = errStr; } interpretPushLong(program, interpretAddString(program, string)); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x458F00 static void op_critter_inven_obj(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_inven_obj", program->name, arg); } } Object* critter = (Object*)data[1]; int type = data[0]; int result = 0; if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { switch (type) { case INVEN_TYPE_WORN: result = (int)inven_worn(critter); break; case INVEN_TYPE_RIGHT_HAND: if (critter == obj_dude) { if (intface_is_item_right_hand() != HAND_LEFT) { result = (int)inven_right_hand(critter); } } else { result = (int)inven_right_hand(critter); } break; case INVEN_TYPE_LEFT_HAND: if (critter == obj_dude) { if (intface_is_item_right_hand() == HAND_LEFT) { result = (int)inven_left_hand(critter); } } else { result = (int)inven_left_hand(critter); } break; case INVEN_TYPE_INV_COUNT: result = critter->data.inventory.length; break; default: int_debug("script error: %s: Error in critter_inven_obj -- wrong type!", program->name); break; } } else { dbg_error(program, "critter_inven_obj", SCRIPT_ERROR_FOLLOWS); debug_printf(" Not a critter!"); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x459088 static void op_obj_set_light_level(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to obj_set_light_level", program->name, arg); } } Object* object = (Object*)data[2]; int lightIntensity = data[1]; int lightDistance = data[0]; if (object == NULL) { dbg_error(program, "obj_set_light_level", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Rect rect; if (lightIntensity != 0) { if (obj_set_light(object, lightDistance, (lightIntensity * 65636) / 100, &rect) == -1) { return; } } else { if (obj_set_light(object, lightDistance, 0, &rect) == -1) { return; } } tile_refresh_rect(&rect, object->elevation); } // 0x459170 static void op_world_map(Program* program) { scripts_request_worldmap(); } // 0x459178 static void op_inven_cmds(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to inven_cmds", program->name, arg); } } Object* obj = (Object*)data[2]; int cmd = data[1]; int index = data[0]; Object* item = NULL; if (obj != NULL) { switch (cmd) { case 13: item = inven_index_ptr(obj, index); break; } } else { // FIXME: Should be inven_cmds. dbg_error(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, (int)item); interpretPushShort(program, VALUE_TYPE_INT); } // 0x459280 static void op_float_msg(Program* program) { // 0x518F00 static int last_color = 1; opcode_t opcode[3]; int data[3]; char* string = NULL; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if (arg == 1) { if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode[arg], data[arg]); } } else { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to float_msg", program->name, arg); } } } Object* obj = (Object*)data[2]; int floatingMessageType = data[0]; int color = colorTable[32747]; int a5 = colorTable[0]; int font = 101; if (obj == NULL) { dbg_error(program, "float_msg", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (string == NULL || *string == '\0') { text_object_remove(obj); tile_refresh_display(); return; } if (obj->elevation != map_elevation) { return; } if (floatingMessageType == FLOATING_MESSAGE_TYPE_COLOR_SEQUENCE) { floatingMessageType = last_color + 1; if (floatingMessageType >= FLOATING_MESSAGE_TYPE_COUNT) { floatingMessageType = FLOATING_MESSAGE_TYPE_BLACK; } last_color = floatingMessageType; } switch (floatingMessageType) { case FLOATING_MESSAGE_TYPE_WARNING: color = colorTable[31744]; a5 = colorTable[0]; font = 103; tile_set_center(obj_dude->tile, TILE_SET_CENTER_REFRESH_WINDOW); break; case FLOATING_MESSAGE_TYPE_NORMAL: case FLOATING_MESSAGE_TYPE_YELLOW: color = colorTable[32747]; break; case FLOATING_MESSAGE_TYPE_BLACK: case FLOATING_MESSAGE_TYPE_PURPLE: case FLOATING_MESSAGE_TYPE_GREY: color = colorTable[10570]; break; case FLOATING_MESSAGE_TYPE_RED: color = colorTable[31744]; break; case FLOATING_MESSAGE_TYPE_GREEN: color = colorTable[992]; break; case FLOATING_MESSAGE_TYPE_BLUE: color = colorTable[31]; break; case FLOATING_MESSAGE_TYPE_NEAR_WHITE: color = colorTable[21140]; break; case FLOATING_MESSAGE_TYPE_LIGHT_RED: color = colorTable[32074]; break; case FLOATING_MESSAGE_TYPE_WHITE: color = colorTable[32767]; break; case FLOATING_MESSAGE_TYPE_DARK_GREY: color = colorTable[8456]; break; case FLOATING_MESSAGE_TYPE_LIGHT_GREY: color = colorTable[15855]; break; } Rect rect; if (text_object_create(obj, string, font, color, a5, &rect) != -1) { tile_refresh_rect(&rect, obj->elevation); } } // 0x4594A0 static void op_metarule(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to metarule", program->name, arg); } } int rule = data[1]; int param = data[0]; int result = 0; switch (rule) { case METARULE_SIGNAL_END_GAME: result = 0; game_user_wants_to_quit = 2; break; case METARULE_FIRST_RUN: result = (map_data.flags & MAP_SAVED) == 0; break; case METARULE_ELEVATOR: scripts_request_elevator(scr_find_obj_from_program(program), param); result = 0; break; case METARULE_PARTY_COUNT: result = getPartyMemberCount(); break; case METARULE_AREA_KNOWN: result = wmAreaVisitedState(param); break; case METARULE_WHO_ON_DRUGS: result = queue_find((Object*)param, EVENT_TYPE_DRUG); break; case METARULE_MAP_KNOWN: result = wmMapIsKnown(param); break; case METARULE_IS_LOADGAME: result = isLoadingGame(); break; case METARULE_CAR_CURRENT_TOWN: result = wmCarCurrentArea(); break; case METARULE_GIVE_CAR_TO_PARTY: result = wmCarGiveToParty(); break; case METARULE_GIVE_CAR_GAS: result = wmCarFillGas(param); break; case METARULE_SKILL_CHECK_TAG: result = skill_is_tagged(param); break; case METARULE_DROP_ALL_INVEN: if (1) { Object* object = (Object*)param; result = item_drop_all(object, object->tile); if (obj_dude == object) { intface_update_items(false, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); intface_update_ac(false); } } break; case METARULE_INVEN_UNWIELD_WHO: if (1) { Object* object = (Object*)param; int hand = HAND_RIGHT; if (object == obj_dude) { if (intface_is_item_right_hand() == HAND_LEFT) { hand = HAND_LEFT; } } result = invenUnwieldFunc(object, hand, 0); if (object == obj_dude) { bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } else { Object* item = inven_left_hand(object); if (item_get_type(item) == ITEM_TYPE_WEAPON) { item->flags &= ~OBJECT_IN_LEFT_HAND; } } } break; case METARULE_GET_WORLDMAP_XPOS: wmGetPartyWorldPos(&result, NULL); break; case METARULE_GET_WORLDMAP_YPOS: wmGetPartyWorldPos(NULL, &result); break; case METARULE_CURRENT_TOWN: if (wmGetPartyCurArea(&result) == -1) { debug_printf("\nIntextra: Error: metarule: current_town"); } break; case METARULE_LANGUAGE_FILTER: config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_LANGUAGE_FILTER_KEY, &result); break; case METARULE_VIOLENCE_FILTER: config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &result); break; case METARULE_WEAPON_DAMAGE_TYPE: if (1) { Object* object = (Object*)param; if (PID_TYPE(object->pid) == OBJ_TYPE_ITEM) { if (item_get_type(object) == ITEM_TYPE_WEAPON) { result = item_w_damage_type(NULL, object); break; } } else { if (art_id(OBJ_TYPE_MISC, 10, 0, 0, 0) == object->fid) { result = DAMAGE_TYPE_EXPLOSION; break; } } dbg_error(program, "metarule:w_damage_type", SCRIPT_ERROR_FOLLOWS); debug_printf("Not a weapon!"); } break; case METARULE_CRITTER_BARTERS: if (1) { Object* object = (Object*)param; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { Proto* proto; proto_ptr(object->pid, &proto); if ((proto->critter.data.flags & CRITTER_BARTER) != 0) { result = 1; } } } break; case METARULE_CRITTER_KILL_TYPE: result = critterGetKillType((Object*)param); break; case METARULE_SET_CAR_CARRY_AMOUNT: if (1) { Proto* proto; if (proto_ptr(PROTO_ID_CAR_TRUNK, &proto) != -1) { proto->item.data.container.maxSize = param; result = 1; } } break; case METARULE_GET_CAR_CARRY_AMOUNT: if (1) { Proto* proto; if (proto_ptr(PROTO_ID_CAR_TRUNK, &proto) != -1) { result = proto->item.data.container.maxSize; } } break; } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x4598BC static void op_anim(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to anim", program->name, arg); } } Object* obj = (Object*)data[2]; int anim = data[1]; int frame = data[0]; if (obj == NULL) { dbg_error(program, "anim", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (anim < ANIM_COUNT) { CritterCombatData* combatData = NULL; if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { combatData = &(obj->data.critter.combat); } anim = correctDeath(obj, anim, true); register_begin(ANIMATION_REQUEST_UNRESERVED); // TODO: Not sure about the purpose, why it handles knock down flag? if (frame == 0) { register_object_animate(obj, anim, 0); if (anim >= ANIM_FALL_BACK && anim <= ANIM_FALL_FRONT_BLOOD) { int fid = art_id(OBJ_TYPE_CRITTER, obj->fid & 0xFFF, anim + 28, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 28); register_object_change_fid(obj, fid, -1); } if (combatData != NULL) { combatData->results &= DAM_KNOCKED_DOWN; } } else { int fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, anim, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); register_object_animate_reverse(obj, anim, 0); if (anim == ANIM_PRONE_TO_STANDING) { fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_FALL_FRONT_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); } else if (anim == ANIM_BACK_TO_STANDING) { fid = art_id(FID_TYPE(obj->fid), obj->fid & 0xFFF, ANIM_FALL_BACK_SF, (obj->fid & 0xF000) >> 12, (obj->fid & 0x70000000) >> 24); } if (combatData != NULL) { combatData->results |= DAM_KNOCKED_DOWN; } register_object_change_fid(obj, fid, -1); } register_end(); } else if (anim == 1000) { if (frame < ROTATION_COUNT) { Rect rect; obj_set_rotation(obj, frame, &rect); tile_refresh_rect(&rect, map_elevation); } } else if (anim == 1010) { Rect rect; obj_set_frame(obj, frame, &rect); tile_refresh_rect(&rect, map_elevation); } else { int_debug("\nScript Error: %s: op_anim: anim out of range", program->name); } } // 0x459B5C static void op_obj_carrying_pid_obj(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to obj_carrying_pid_obj", program->name, arg); } } Object* object = (Object*)data[1]; int pid = data[0]; Object* result = NULL; if (object != NULL) { result = inven_pid_is_carried(object, pid); } else { dbg_error(program, "obj_carrying_pid_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, (int)result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x459C20 static void op_reg_anim_func(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_func", program->name, arg); } } int cmd = data[1]; int param = data[0]; if (!isInCombat()) { switch (cmd) { case OP_REG_ANIM_FUNC_BEGIN: register_begin(param); break; case OP_REG_ANIM_FUNC_CLEAR: register_clear((Object*)param); break; case OP_REG_ANIM_FUNC_END: register_end(); break; } } } // 0x459CD4 static void op_reg_anim_animate(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_animate", program->name, arg); } } Object* object = (Object*)data[2]; int anim = data[1]; int delay = data[0]; if (!isInCombat()) { int violenceLevel = VIOLENCE_LEVEL_NONE; if (anim != 20 || object == NULL || object->pid != 0x100002F || (config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_VIOLENCE_LEVEL_KEY, &violenceLevel) && violenceLevel >= 2)) { if (object != NULL) { register_object_animate(object, anim, delay); } else { dbg_error(program, "reg_anim_animate", SCRIPT_ERROR_OBJECT_IS_NULL); } } } } // 0x459DC4 static void op_reg_anim_animate_reverse(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_animate_reverse", program->name, arg); } } Object* object = (Object*)data[2]; int anim = data[1]; int delay = data[0]; if (!isInCombat()) { if (object != NULL) { register_object_animate_reverse(object, anim, delay); } else { dbg_error(program, "reg_anim_animate_reverse", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // 0x459E74 static void op_reg_anim_obj_move_to_obj(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_obj_move_to_obj", program->name, arg); } } Object* object = (Object*)data[2]; Object* dest = (Object*)data[1]; int delay = data[0]; if (!isInCombat()) { if (object != NULL) { register_object_move_to_object(object, dest, -1, delay); } else { dbg_error(program, "reg_anim_obj_move_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // 0x459F28 static void op_reg_anim_obj_run_to_obj(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_obj_run_to_obj", program->name, arg); } } Object* object = (Object*)data[2]; Object* dest = (Object*)data[1]; int delay = data[0]; if (!isInCombat()) { if (object != NULL) { register_object_run_to_object(object, dest, -1, delay); } else { dbg_error(program, "reg_anim_obj_run_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // 0x459FDC static void op_reg_anim_obj_move_to_tile(Program* prg) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(prg); data[arg] = interpretPopLong(prg); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(prg, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_obj_move_to_tile", prg->name, arg); } } Object* object = (Object*)data[2]; int tile = data[1]; int delay = data[0]; if (!isInCombat()) { if (object != NULL) { register_object_move_to_tile(object, tile, object->elevation, -1, delay); } else { dbg_error(prg, "reg_anim_obj_move_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // 0x45A094 static void op_reg_anim_obj_run_to_tile(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_obj_run_to_tile", program->name, arg); } } Object* object = (Object*)data[2]; int tile = data[1]; int delay = data[0]; if (!isInCombat()) { if (object != NULL) { register_object_run_to_tile(object, tile, object->elevation, -1, delay); } else { dbg_error(program, "reg_anim_obj_run_to_tile", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // 0x45A14C static void op_play_gmovie(Program* program) { // 0x453F9C static const unsigned short word_453F9C[MOVIE_COUNT] = { GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, GAME_MOVIE_FADE_IN | GAME_MOVIE_FADE_OUT | GAME_MOVIE_PAUSE_MUSIC, }; program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to play_gmovie", program->name); } gdialogDisableBK(); if (gmovie_play(data, word_453F9C[data]) == -1) { debug_printf("\nError playing movie %d!", data); } gdialogEnableBK(); program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45A200 static void op_add_mult_objs_to_inven(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to add_mult_objs_to_inven", program->name, arg); } } Object* object = (Object*)data[2]; Object* item = (Object*)data[1]; int quantity = data[0]; if (object == NULL || item == NULL) { return; } if (quantity < 0) { quantity = 1; } else if (quantity > 99999) { quantity = 500; } if (item_add_force(object, item, quantity) == 0) { Rect rect; obj_disconnect(item, &rect); tile_refresh_rect(&rect, item->elevation); } } // 0x45A2D4 static void op_rm_mult_objs_from_inven(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to rm_mult_objs_from_inven", program->name, arg); } } Object* owner = (Object*)data[2]; Object* item = (Object*)data[1]; int quantityToRemove = data[0]; if (owner == NULL || item == NULL) { // FIXME: Ruined stack. return; } bool itemWasEquipped = (item->flags & OBJECT_EQUIPPED) != 0; int quantity = item_count(owner, item); if (quantity > quantityToRemove) { quantity = quantityToRemove; } if (quantity != 0) { if (item_remove_mult(owner, item, quantity) == 0) { Rect updatedRect; obj_connect(item, 1, 0, &updatedRect); if (itemWasEquipped) { if (owner == obj_dude) { bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } } } interpretPushLong(program, quantity); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45A40C static void op_get_month(Program* program) { int month; game_time_date(&month, NULL, NULL); interpretPushLong(program, month); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45A43C static void op_get_day(Program* program) { int day; game_time_date(NULL, &day, NULL); interpretPushLong(program, day); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45A46C static void op_explosion(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to explosion", program->name, arg); } } int tile = data[2]; int elevation = data[1]; int maxDamage = data[0]; if (tile == -1) { debug_printf("\nError: explosion: bad tile_num!"); return; } int minDamage = 1; if (maxDamage == 0) { minDamage = 0; } scripts_request_explosion(tile, elevation, minDamage, maxDamage); } // 0x45A528 static void op_days_since_visited(Program* program) { int days; if (map_data.lastVisitTime != 0) { days = (game_time() - map_data.lastVisitTime) / GAME_TIME_TICKS_PER_DAY; } else { days = -1; } interpretPushLong(program, days); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45A56C static void op_gsay_start(Program* program) { program->flags |= PROGRAM_FLAG_0x20; if (gdialogStart() != 0) { program->flags &= ~PROGRAM_FLAG_0x20; interpretError("Error starting dialog."); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45A5B0 static void op_gsay_end(Program* program) { program->flags |= PROGRAM_FLAG_0x20; gdialogGo(); program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45A5D4 static void op_gsay_reply(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[2]; int data[2]; char* string = NULL; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { if (arg == 0) { if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode[arg], data[arg]); } else { interpretError("script error: %s: invalid arg %d to gsay_reply", program->name, arg); } } else { interpretError("script error: %s: invalid arg %d to gsay_reply", program->name, arg); } } } int messageListId = data[1]; int messageId = data[0]; if (string != NULL) { gdialogReplyStr(program, messageListId, string); } else { gdialogReply(program, messageListId, messageId); } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45A6C4 static void op_gsay_option(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[4]; int data[4]; // TODO: Original code is slightly different, does not use loop for first // two args, but uses loop for two last args. char* string = NULL; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { if (arg == 2) { if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode[arg], data[arg]); } else { interpretError("script error: %s: invalid arg %d to gsay_option", program->name, arg); } } else { interpretError("script error: %s: invalid arg %d to gsay_option", program->name, arg); } } } int messageListId = data[3]; int messageId = data[2]; int proc = data[1]; int reaction = data[0]; // TODO: Not sure about this, needs testing. if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* procName = interpretGetString(program, opcode[1], data[1]); if (string != NULL) { gdialogOptionStr(data[3], string, procName, reaction); } else { gdialogOption(data[3], data[2], procName, reaction); } program->flags &= ~PROGRAM_FLAG_0x20; return; } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 3 to sayOption"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (string != NULL) { gdialogOptionProcStr(data[3], string, proc, reaction); program->flags &= ~PROGRAM_FLAG_0x20; } else { gdialogOptionProc(data[3], data[2], proc, reaction); program->flags &= ~PROGRAM_FLAG_0x20; } } // 0x45A8AC static void op_gsay_message(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[3]; int data[3]; char* string = NULL; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { if (arg == 1) { if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode[arg], data[arg]); } else { interpretError("script error: %s: invalid arg %d to gsay_message", program->name, arg); } } else { interpretError("script error: %s: invalid arg %d to gsay_message", program->name, arg); } } } int messageListId = data[2]; int messageId = data[1]; int reaction = data[0]; if (string != NULL) { gdialogReplyStr(program, messageListId, string); } else { gdialogReply(program, messageListId, messageId); } gdialogOption(-2, -2, NULL, 50); gdialogSayMessage(); program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45A9B4 static void op_giq_option(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[5]; int data[5]; char* string = NULL; for (int arg = 0; arg < 5; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { if (arg == 2) { if ((opcode[arg] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { string = interpretGetString(program, opcode[arg], data[arg]); } else { interpretError("script error: %s: invalid arg %d to giq_option", program->name, arg); } } else { interpretError("script error: %s: invalid arg %d to giq_option", program->name, arg); } } } int iq = data[4]; int messageListId = data[3]; int messageId = data[2]; int proc = data[1]; int reaction = data[0]; int intelligence = critterGetStat(obj_dude, STAT_INTELLIGENCE); intelligence += perk_level(obj_dude, PERK_SMOOTH_TALKER); if (iq < 0) { if (-intelligence < iq) { program->flags &= ~PROGRAM_FLAG_0x20; return; } } else { if (intelligence < iq) { program->flags &= ~PROGRAM_FLAG_0x20; return; } } if ((opcode[1] & VALUE_TYPE_MASK) == VALUE_TYPE_STRING) { char* procName = interpretGetString(program, opcode[1], data[1]); if (string != NULL) { gdialogOptionStr(messageListId, string, procName, reaction); } else { gdialogOption(messageListId, messageId, procName, reaction); } program->flags &= ~PROGRAM_FLAG_0x20; return; } if ((opcode[1] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("Invalid arg 4 to sayOption"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (string != NULL) { gdialogOptionProcStr(messageListId, string, proc, reaction); program->flags &= ~PROGRAM_FLAG_0x20; } else { gdialogOptionProc(messageListId, messageId, proc, reaction); program->flags &= ~PROGRAM_FLAG_0x20; } } // 0x45AB90 static void op_poison(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to poison", program->name, arg); } } Object* obj = (Object*)data[1]; int amount = data[0]; if (obj == NULL) { dbg_error(program, "poison", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (critter_adjust_poison(obj, amount) != 0) { debug_printf("\nScript Error: poison: adjust failed!"); } } // 0x45AC44 static void op_get_poison(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to get_poison", program->name); } Object* obj = (Object*)data; int poison = 0; if (obj != NULL) { if (PID_TYPE(obj->pid) == OBJ_TYPE_CRITTER) { poison = critter_get_poison(obj); } else { debug_printf("\nScript Error: get_poison: who is not a critter!"); } } else { dbg_error(program, "get_poison", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, poison); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45ACF4 static void op_party_add(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to party_add", program->name); } Object* object = (Object*)data; if (object == NULL) { dbg_error(program, "party_add", SCRIPT_ERROR_OBJECT_IS_NULL); return; } partyMemberAdd(object); } // 0x45AD68 static void op_party_remove(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to party_remove", program->name); } Object* object = (Object*)data; if (object == NULL) { dbg_error(program, "party_remove", SCRIPT_ERROR_OBJECT_IS_NULL); return; } partyMemberRemove(object); } // 0x45ADDC static void op_reg_anim_animate_forever(Program* prg) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(prg); data[arg] = interpretPopLong(prg); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(prg, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_animate_forever", prg->name, arg); } } Object* obj = (Object*)data[1]; int anim = data[0]; if (!isInCombat()) { if (obj != NULL) { register_object_animate_forever(obj, anim, -1); } else { dbg_error(prg, "reg_anim_animate_forever", SCRIPT_ERROR_OBJECT_IS_NULL); } } } // 0x45AE8C static void op_critter_injure(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_injure", program->name, arg); } } Object* critter = (Object*)data[1]; int flags = data[0]; if (critter == NULL) { dbg_error(program, "critter_injure", SCRIPT_ERROR_OBJECT_IS_NULL); return; } bool reverse = (flags & DAM_PERFORM_REVERSE) != 0; flags &= DAM_CRIP; if (reverse) { critter->data.critter.combat.results &= ~flags; } else { critter->data.critter.combat.results |= flags; } if (critter == obj_dude) { if ((flags & DAM_CRIP_ARM_ANY) != 0) { int leftItemAction; int rightItemAction; intface_get_item_states(&leftItemAction, &rightItemAction); intface_update_items(true, leftItemAction, rightItemAction); } } } // 0x45AF7C static void op_combat_is_initialized(Program* program) { interpretPushLong(program, isInCombat()); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45AFA0 static void op_gdialog_barter(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to gdialog_barter", program->name); } if (gdActivateBarter(data) == -1) { debug_printf("\nScript Error: gdialog_barter: failed"); } } // 0x45B010 static void op_difficulty_level(Program* program) { int gameDifficulty; if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_GAME_DIFFICULTY_KEY, &gameDifficulty)) { gameDifficulty = GAME_DIFFICULTY_NORMAL; } interpretPushLong(program, gameDifficulty); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B05C static void op_running_burning_guy(Program* program) { int runningBurningGuy; if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_RUNNING_BURNING_GUY_KEY, &runningBurningGuy)) { runningBurningGuy = 1; } interpretPushLong(program, runningBurningGuy); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B0A8 static void op_inven_unwield(Program* program) { Object* obj; int v1; obj = scr_find_obj_from_program(program); v1 = 1; if (obj == obj_dude && !intface_is_item_right_hand()) { v1 = 0; } inven_unwield(obj, v1); } // 0x45B0D8 static void op_obj_is_locked(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_is_locked", program->name); } Object* object = (Object*)data; bool locked = false; if (object != NULL) { locked = obj_is_locked(object); } else { dbg_error(program, "obj_is_locked", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, locked); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B16C static void op_obj_lock(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_lock", program->name); } Object* object = (Object*)data; if (object != NULL) { obj_lock(object); } else { dbg_error(program, "obj_lock", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B1E0 static void op_obj_unlock(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_unlock", program->name); } Object* object = (Object*)data; if (object != NULL) { obj_unlock(object); } else { dbg_error(program, "obj_unlock", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B254 static void op_obj_is_open(Program* s) { opcode_t opcode = interpretPopShort(s); int data = interpretPopLong(s); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(s, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_is_open", s->name); } Object* object = (Object*)data; bool isOpen = false; if (object != NULL) { isOpen = obj_is_open(object); } else { dbg_error(s, "obj_is_open", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(s, isOpen); interpretPushShort(s, VALUE_TYPE_INT); } // 0x45B2E8 static void op_obj_open(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_open", program->name); } Object* object = (Object*)data; if (object != NULL) { obj_open(object); } else { dbg_error(program, "obj_open", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B35C static void op_obj_close(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_close", program->name); } Object* object = (Object*)data; if (object != NULL) { obj_close(object); } else { dbg_error(program, "obj_close", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B3D0 static void op_game_ui_disable(Program* program) { game_ui_disable(0); } // 0x45B3D8 static void op_game_ui_enable(Program* program) { game_ui_enable(); } // 0x45B3E0 static void op_game_ui_is_disabled(Program* program) { interpretPushLong(program, game_ui_is_disabled()); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B404 static void op_gfade_out(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to gfade_out", program->name); } if (data != 0) { palette_fade_to(black_palette); } else { dbg_error(program, "gfade_out", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B47C static void op_gfade_in(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to gfade_in", program->name); } if (data != 0) { palette_fade_to(cmap); } else { dbg_error(program, "gfade_in", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B4F4 static void op_item_caps_total(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to item_caps_total", program->name); } Object* object = (Object*)data; int amount = 0; if (object != NULL) { amount = item_caps_total(object); } else { dbg_error(program, "item_caps_total", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, amount); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B588 static void op_item_caps_adjust(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to item_caps_adjust", program->name, arg); } } Object* object = (Object*)data[1]; int amount = data[0]; int rc = -1; if (object != NULL) { rc = item_caps_adjust(object, amount); } else { dbg_error(program, "item_caps_adjust", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, rc); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B64C static void op_anim_action_frame(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to anim_action_frame", program->name, arg); } } Object* object = (Object*)data[1]; int anim = data[0]; int actionFrame = 0; if (object != NULL) { int fid = art_id(FID_TYPE(object->fid), object->fid & 0xFFF, anim, 0, object->rotation); CacheEntry* frmHandle; Art* frm = art_ptr_lock(fid, &frmHandle); if (frm != NULL) { actionFrame = art_frame_action_frame(frm); art_ptr_unlock(frmHandle); } } else { dbg_error(program, "anim_action_frame", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, actionFrame); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B740 static void op_reg_anim_play_sfx(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if (arg == 1) { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("script error: %s: invalid arg %d to reg_anim_play_sfx", program->name, arg); } } else { if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to reg_anim_play_sfx", program->name, arg); } } } Object* obj = (Object*)data[2]; int name = data[1]; int delay = data[0]; char* soundEffectName = interpretGetString(program, opcode[1], name); if (soundEffectName == NULL) { dbg_error(program, "reg_anim_play_sfx", SCRIPT_ERROR_FOLLOWS); debug_printf(" Can't match string!"); } if (obj != NULL) { register_object_play_sfx(obj, soundEffectName, delay); } else { dbg_error(program, "reg_anim_play_sfx", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45B840 static void op_critter_mod_skill(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_mod_skill", program->name, arg); } } Object* critter = (Object*)data[2]; int skill = data[1]; int points = data[0]; if (critter != NULL && points != 0) { if (PID_TYPE(critter->pid) == OBJ_TYPE_CRITTER) { if (critter == obj_dude) { int normalizedPoints = abs(points); if (skill_is_tagged(skill)) { // Halve number of skill points. Increment/decrement skill // points routines handle that. normalizedPoints /= 2; } if (points > 0) { // Increment skill points one by one. for (int it = 0; it < normalizedPoints; it++) { skill_inc_point_force(obj_dude, skill); } } else { // Decrement skill points one by one. for (int it = 0; it < normalizedPoints; it++) { skill_dec_point_force(obj_dude, skill); } } // TODO: Checking for critter is dude twice probably means this // is inlined function. if (critter == obj_dude) { int leftItemAction; int rightItemAction; intface_get_item_states(&leftItemAction, &rightItemAction); intface_update_items(false, leftItemAction, rightItemAction); } } else { dbg_error(program, "critter_mod_skill", SCRIPT_ERROR_FOLLOWS); debug_printf(" Can't modify anyone except obj_dude!"); } } } else { dbg_error(program, "critter_mod_skill", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, 0); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45B9C4 static void op_sfx_build_char_name(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to sfx_build_char_name", program->name, arg); } } Object* obj = (Object*)data[2]; int anim = data[1]; int extra = data[0]; int stringOffset = 0; if (obj != NULL) { char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_character_sfx_name(obj, anim, extra)); stringOffset = interpretAddString(program, soundEffectName); } else { dbg_error(program, "sfx_build_char_name", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BAA8 static void op_sfx_build_ambient_name(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to sfx_build_ambient_name", program->name); } char* baseName = interpretGetString(program, opcode, data); char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_ambient_sfx_name(baseName)); int stringOffset = interpretAddString(program, soundEffectName); interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BB54 static void op_sfx_build_interface_name(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to sfx_build_interface_name", program->name); } char* baseName = interpretGetString(program, opcode, data); char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_interface_sfx_name(baseName)); int stringOffset = interpretAddString(program, soundEffectName); interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BC00 static void op_sfx_build_item_name(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to sfx_build_item_name", program->name); } const char* baseName = interpretGetString(program, opcode, data); char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_interface_sfx_name(baseName)); int stringOffset = interpretAddString(program, soundEffectName); interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BCAC static void op_sfx_build_weapon_name(Program* program) { opcode_t opcode[4]; int data[4]; for (int arg = 0; arg < 4; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to sfx_build_weapon_name", program->name, arg); } } int weaponSfxType = data[3]; Object* weapon = (Object*)data[2]; int hitMode = data[1]; Object* target = (Object*)data[0]; char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_weapon_sfx_name(weaponSfxType, weapon, hitMode, target)); int stringOffset = interpretAddString(program, soundEffectName); interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BD7C static void op_sfx_build_scenery_name(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to sfx_build_scenery_name", program->name, arg); } } int action = data[1]; int actionType = data[0]; char* baseName = interpretGetString(program, opcode[2], data[2]); char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_scenery_sfx_name(actionType, action, baseName)); int stringOffset = interpretAddString(program, soundEffectName); interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BE58 static void op_sfx_build_open_name(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to sfx_build_open_name", program->name, arg); } } Object* object = (Object*)data[1]; int action = data[0]; int stringOffset = 0; if (object != NULL) { char soundEffectName[16]; strcpy(soundEffectName, gsnd_build_open_sfx_name(object, action)); stringOffset = interpretAddString(program, soundEffectName); } else { dbg_error(program, "sfx_build_open_name", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45BF38 static void op_attack_setup(Program* program) { opcode_t opcodes[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcodes[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcodes[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcodes[arg], data[arg]); } if ((opcodes[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to attack_setup", program->name, arg); } } Object* attacker = (Object*)data[1]; Object* defender = (Object*)data[0]; program->flags |= PROGRAM_FLAG_0x20; if (attacker != NULL) { if (!critter_is_active(attacker) || (attacker->flags & OBJECT_HIDDEN) != 0) { debug_printf("\n But is already dead or invisible"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (!critter_is_active(defender) || (defender->flags & OBJECT_HIDDEN) != 0) { debug_printf("\n But target is already dead or invisible"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if ((defender->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0) { debug_printf("\n But target is AFRAID"); program->flags &= ~PROGRAM_FLAG_0x20; return; } if (isInCombat()) { if ((attacker->data.critter.combat.maneuver & CRITTER_MANEUVER_0x01) == 0) { attacker->data.critter.combat.maneuver |= CRITTER_MANEUVER_0x01; attacker->data.critter.combat.whoHitMe = defender; } } else { STRUCT_664980 attack; attack.attacker = attacker; attack.defender = defender; attack.actionPointsBonus = 0; attack.accuracyBonus = 0; attack.damageBonus = 0; attack.minDamage = 0; attack.maxDamage = INT_MAX; // FIXME: Something bad here, when attacker and defender are // the same object, these objects are used as flags, which // are later used in 0x422F3C as flags of defender. if (data[1] == data[0]) { attack.field_1C = 1; attack.field_20 = data[1]; attack.field_24 = data[0]; } else { attack.field_1C = 0; } scripts_request_combat(&attack); } } program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45C0E8 static void op_destroy_mult_objs(Program* program) { program->flags |= PROGRAM_FLAG_0x20; opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to destroy_mult_objs", program->name, arg); } } Object* object = (Object*)data[1]; int quantity = data[0]; Object* self = scr_find_obj_from_program(program); bool isSelf = self == object; int result = 0; if (PID_TYPE(object->pid) == OBJ_TYPE_CRITTER) { combat_delete_critter(object); } Object* owner = obj_top_environment(object); if (owner != NULL) { int quantityToDestroy = item_count(owner, object); if (quantityToDestroy > quantity) { quantityToDestroy = quantity; } item_remove_mult(owner, object, quantityToDestroy); if (owner == obj_dude) { bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } obj_connect(object, 1, 0, NULL); if (isSelf) { object->sid = -1; object->flags |= (OBJECT_HIDDEN | OBJECT_TEMPORARY); } else { register_clear(object); obj_erase_object(object, NULL); } result = quantityToDestroy; } else { register_clear(object); Rect rect; obj_erase_object(object, &rect); tile_refresh_rect(&rect, map_elevation); } interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); program->flags &= ~PROGRAM_FLAG_0x20; if (isSelf) { program->flags |= PROGRAM_FLAG_0x0100; } } // 0x45C290 static void op_use_obj_on_obj(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to use_obj_on_obj", program->name, arg); } } Object* item = (Object*)data[1]; Object* target = (Object*)data[0]; if (item == NULL) { dbg_error(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (target == NULL) { dbg_error(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Script* script; int sid = scr_find_sid_from_program(program); if (scr_ptr(sid, &script) == -1) { // FIXME: Should be SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID. dbg_error(program, "use_obj_on_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Object* self = scr_find_obj_from_program(program); if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { action_use_an_item_on_object(self, target, item); } else { obj_use_item_on(self, target, item); } } // 0x45C3B0 static void op_endgame_slideshow(Program* program) { program->flags |= PROGRAM_FLAG_0x20; scripts_request_endgame_slideshow(); program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45C3D0 static void op_move_obj_inven_to_obj(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to move_obj_inven_to_obj", program->name, arg); } } Object* object1 = (Object*)data[1]; Object* object2 = (Object*)data[0]; if (object1 == NULL) { dbg_error(program, "move_obj_inven_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } if (object2 == NULL) { dbg_error(program, "move_obj_inven_to_obj", SCRIPT_ERROR_OBJECT_IS_NULL); return; } Object* oldArmor = NULL; Object* item2 = NULL; if (object1 == obj_dude) { oldArmor = inven_worn(object1); } else { item2 = inven_right_hand(object1); } if (object1 != obj_dude && item2 != NULL) { int flags = 0; if ((item2->flags & 0x01000000) != 0) { flags |= 0x01000000; } if ((item2->flags & 0x02000000) != 0) { flags |= 0x02000000; } correctFidForRemovedItem(object1, item2, flags); } item_move_all(object1, object2); if (object1 == obj_dude) { if (oldArmor != NULL) { adjust_ac(obj_dude, oldArmor, NULL); } proto_dude_update_gender(); bool animated = !game_ui_is_disabled(); intface_update_items(animated, INTERFACE_ITEM_ACTION_DEFAULT, INTERFACE_ITEM_ACTION_DEFAULT); } } // 0x45C54C static void op_endgame_movie(Program* program) { program->flags |= PROGRAM_FLAG_0x20; endgame_movie(); program->flags &= ~PROGRAM_FLAG_0x20; } // 0x45C56C static void op_obj_art_fid(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_art_fid", program->name); } Object* object = (Object*)data; int fid = 0; if (object != NULL) { fid = object->fid; } else { dbg_error(program, "obj_art_fid", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, fid); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C5F8 static void op_art_anim(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to art_anim", program->name); } interpretPushLong(program, FID_ANIM_TYPE(data)); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C66C static void op_party_member_obj(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to party_member_obj", program->name); } Object* object = partyMemberFindObjFromPid(data); interpretPushLong(program, (int)object); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C6DC static void op_rotation_to_tile(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to rotation_to_tile", program->name, arg); } } int tile1 = data[1]; int tile2 = data[0]; int rotation = tile_dir(tile1, tile2); interpretPushLong(program, rotation); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C778 static void op_jam_lock(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to jam_lock", program->name); } Object* object = (Object*)data; obj_jam_lock(object); } // 0x45C7D4 static void op_gdialog_set_barter_mod(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to gdialog_set_barter_mod", program->name); } gdialogSetBarterMod(data); } // 0x45C830 static void op_combat_difficulty(Program* program) { int combatDifficulty; if (!config_get_value(&game_config, GAME_CONFIG_PREFERENCES_KEY, GAME_CONFIG_COMBAT_DIFFICULTY_KEY, &combatDifficulty)) { combatDifficulty = 0; } interpretPushLong(program, combatDifficulty); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C878 static void op_obj_on_screen(Program* program) { // 0x453FC0 static Rect rect = { 0, 0, 640, 480 }; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_on_screen", program->name); } Object* object = (Object*)data; int result = 0; if (object != NULL) { if (map_elevation == object->elevation) { Rect objectRect; obj_bound(object, &objectRect); if (rect_inside_bound(&objectRect, &rect, &objectRect) == 0) { result = 1; } } } else { dbg_error(program, "obj_on_screen", SCRIPT_ERROR_OBJECT_IS_NULL); } //debug_printf("ObjOnScreen: %d\n", result); interpretPushLong(program, result); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C93C static void op_critter_is_fleeing(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to critter_is_fleeing", program->name); } Object* obj = (Object*)data; bool fleeing = false; if (obj != NULL) { fleeing = (obj->data.critter.combat.maneuver & CRITTER_MANUEVER_FLEEING) != 0; } else { dbg_error(program, "critter_is_fleeing", SCRIPT_ERROR_OBJECT_IS_NULL); } interpretPushLong(program, fleeing); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45C9DC static void op_critter_set_flee_state(Program* program) { opcode_t opcode[2]; int data[2]; for (int arg = 0; arg < 2; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to critter_set_flee_state", program->name, arg); } } Object* object = (Object*)data[1]; int fleeing = data[0]; if (object != NULL) { if (fleeing != 0) { object->data.critter.combat.maneuver |= CRITTER_MANUEVER_FLEEING; } else { object->data.critter.combat.maneuver &= ~CRITTER_MANUEVER_FLEEING; } } else { dbg_error(program, "critter_set_flee_state", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45CA84 static void op_terminate_combat(Program* program) { if (isInCombat()) { game_user_wants_to_quit = 1; Object* self = scr_find_obj_from_program(program); if (self != NULL) { if (PID_TYPE(self->pid) == OBJ_TYPE_CRITTER) { self->data.critter.combat.maneuver |= CRITTER_MANEUVER_STOP_ATTACKING; self->data.critter.combat.whoHitMe = NULL; combatAIInfoSetLastTarget(self, NULL); } } } } // 0x45CAC8 static void op_debug_msg(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_STRING) { interpretError("script error: %s: invalid arg to debug_msg", program->name); } char* string = interpretGetString(program, opcode, data); if (string != NULL) { bool showScriptMessages = false; configGetBool(&game_config, GAME_CONFIG_DEBUG_KEY, GAME_CONFIG_SHOW_SCRIPT_MESSAGES_KEY, &showScriptMessages); if (showScriptMessages) { debug_printf("\n"); debug_printf(string); } } } // 0x45CB70 static void op_critter_stop_attacking(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to critter_stop_attacking", program->name); } Object* obj = (Object*)data; if (obj != NULL) { obj->data.critter.combat.maneuver |= CRITTER_MANEUVER_STOP_ATTACKING; obj->data.critter.combat.whoHitMe = NULL; combatAIInfoSetLastTarget(obj, NULL); } else { dbg_error(program, "critter_stop_attacking", SCRIPT_ERROR_OBJECT_IS_NULL); } } // 0x45CBF8 static void op_tile_contains_pid_obj(Program* program) { opcode_t opcode[3]; int data[3]; for (int arg = 0; arg < 3; arg++) { opcode[arg] = interpretPopShort(program); data[arg] = interpretPopLong(program); if (opcode[arg] == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode[arg], data[arg]); } if ((opcode[arg] & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg %d to tile_contains_pid_obj", program->name, arg); } } int tile = data[2]; int elevation = data[1]; int pid = data[0]; Object* found = NULL; if (tile != -1) { Object* object = obj_find_first_at_tile(elevation, tile); while (object != NULL) { if (object->pid == pid) { found = object; break; } object = obj_find_next_at_tile(); } } interpretPushLong(program, (int)found); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45CCC8 static void op_obj_name(Program* program) { // 0x518F04 static char* strName = _aCritter; opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to obj_name", program->name); } Object* obj = (Object*)data; if (obj != NULL) { strName = object_name(obj); } else { dbg_error(program, "obj_name", SCRIPT_ERROR_OBJECT_IS_NULL); } int stringOffset = interpretAddString(program, strName); interpretPushLong(program, stringOffset); interpretPushShort(program, VALUE_TYPE_DYNAMIC_STRING); } // 0x45CD64 static void op_get_pc_stat(Program* program) { opcode_t opcode = interpretPopShort(program); int data = interpretPopLong(program); if (opcode == VALUE_TYPE_DYNAMIC_STRING) { interpretDecStringRef(program, opcode, data); } if ((opcode & VALUE_TYPE_MASK) != VALUE_TYPE_INT) { interpretError("script error: %s: invalid arg to get_pc_stat", program->name); } int value = stat_pc_get(data); interpretPushLong(program, value); interpretPushShort(program, VALUE_TYPE_INT); } // 0x45CDD4 void intExtraClose() { } // 0x45CDD8 void initIntExtra() { interpretAddFunc(0x80A1, op_give_exp_points); interpretAddFunc(0x80A2, op_scr_return); interpretAddFunc(0x80A3, op_play_sfx); interpretAddFunc(0x80A4, op_obj_name); interpretAddFunc(0x80A5, op_sfx_build_open_name); interpretAddFunc(0x80A6, op_get_pc_stat); interpretAddFunc(0x80A7, op_tile_contains_pid_obj); interpretAddFunc(0x80A8, op_set_map_start); interpretAddFunc(0x80A9, op_override_map_start); interpretAddFunc(0x80AA, op_has_skill); interpretAddFunc(0x80AB, op_using_skill); interpretAddFunc(0x80AC, op_roll_vs_skill); interpretAddFunc(0x80AD, op_skill_contest); interpretAddFunc(0x80AE, op_do_check); interpretAddFunc(0x80AF, op_is_success); interpretAddFunc(0x80B0, op_is_critical); interpretAddFunc(0x80B1, op_how_much); interpretAddFunc(0x80B2, op_mark_area_known); interpretAddFunc(0x80B3, op_reaction_influence); interpretAddFunc(0x80B4, op_random); interpretAddFunc(0x80B5, op_roll_dice); interpretAddFunc(0x80B6, op_move_to); interpretAddFunc(0x80B7, op_create_object_sid); interpretAddFunc(0x80B8, op_display_msg); interpretAddFunc(0x80B9, op_script_overrides); interpretAddFunc(0x80BA, op_obj_is_carrying_obj_pid); interpretAddFunc(0x80BB, op_tile_contains_obj_pid); interpretAddFunc(0x80BC, op_self_obj); interpretAddFunc(0x80BD, op_source_obj); interpretAddFunc(0x80BE, op_target_obj); interpretAddFunc(0x80BF, op_dude_obj); interpretAddFunc(0x80C0, op_obj_being_used_with); interpretAddFunc(0x80C1, op_local_var); interpretAddFunc(0x80C2, op_set_local_var); interpretAddFunc(0x80C3, op_map_var); interpretAddFunc(0x80C4, op_set_map_var); interpretAddFunc(0x80C5, op_global_var); interpretAddFunc(0x80C6, op_set_global_var); interpretAddFunc(0x80C7, op_script_action); interpretAddFunc(0x80C8, op_obj_type); interpretAddFunc(0x80C9, op_obj_item_subtype); interpretAddFunc(0x80CA, op_get_critter_stat); interpretAddFunc(0x80CB, op_set_critter_stat); interpretAddFunc(0x80CC, op_animate_stand_obj); interpretAddFunc(0x80CD, op_animate_stand_reverse_obj); interpretAddFunc(0x80CE, op_animate_move_obj_to_tile); interpretAddFunc(0x80CF, op_tile_in_tile_rect); interpretAddFunc(0x80D0, op_attack); interpretAddFunc(0x80D1, op_make_daytime); interpretAddFunc(0x80D2, op_tile_distance); interpretAddFunc(0x80D3, op_tile_distance_objs); interpretAddFunc(0x80D4, op_tile_num); interpretAddFunc(0x80D5, op_tile_num_in_direction); interpretAddFunc(0x80D6, op_pickup_obj); interpretAddFunc(0x80D7, op_drop_obj); interpretAddFunc(0x80D8, op_add_obj_to_inven); interpretAddFunc(0x80D9, op_rm_obj_from_inven); interpretAddFunc(0x80DA, op_wield_obj_critter); interpretAddFunc(0x80DB, op_use_obj); interpretAddFunc(0x80DC, op_obj_can_see_obj); interpretAddFunc(0x80DD, op_attack); interpretAddFunc(0x80DE, op_start_gdialog); interpretAddFunc(0x80DF, op_end_dialogue); interpretAddFunc(0x80E0, op_dialogue_reaction); interpretAddFunc(0x80E1, op_metarule3); interpretAddFunc(0x80E2, op_set_map_music); interpretAddFunc(0x80E3, op_set_obj_visibility); interpretAddFunc(0x80E4, op_load_map); interpretAddFunc(0x80E5, op_wm_area_set_pos); interpretAddFunc(0x80E6, op_set_exit_grids); interpretAddFunc(0x80E7, op_anim_busy); interpretAddFunc(0x80E8, op_critter_heal); interpretAddFunc(0x80E9, op_set_light_level); interpretAddFunc(0x80EA, op_game_time); interpretAddFunc(0x80EB, op_game_time_in_seconds); interpretAddFunc(0x80EC, op_elevation); interpretAddFunc(0x80ED, op_kill_critter); interpretAddFunc(0x80EE, op_kill_critter_type); interpretAddFunc(0x80EF, op_critter_damage); interpretAddFunc(0x80F0, op_add_timer_event); interpretAddFunc(0x80F1, op_rm_timer_event); interpretAddFunc(0x80F2, op_game_ticks); interpretAddFunc(0x80F3, op_has_trait); interpretAddFunc(0x80F4, op_destroy_object); interpretAddFunc(0x80F5, op_obj_can_hear_obj); interpretAddFunc(0x80F6, op_game_time_hour); interpretAddFunc(0x80F7, op_fixed_param); interpretAddFunc(0x80F8, op_tile_is_visible); interpretAddFunc(0x80F9, op_dialogue_system_enter); interpretAddFunc(0x80FA, op_action_being_used); interpretAddFunc(0x80FB, op_critter_state); interpretAddFunc(0x80FC, op_game_time_advance); interpretAddFunc(0x80FD, op_radiation_inc); interpretAddFunc(0x80FE, op_radiation_dec); interpretAddFunc(0x80FF, op_critter_attempt_placement); interpretAddFunc(0x8100, op_obj_pid); interpretAddFunc(0x8101, op_cur_map_index); interpretAddFunc(0x8102, op_critter_add_trait); interpretAddFunc(0x8103, op_critter_rm_trait); interpretAddFunc(0x8104, op_proto_data); interpretAddFunc(0x8105, op_message_str); interpretAddFunc(0x8106, op_critter_inven_obj); interpretAddFunc(0x8107, op_obj_set_light_level); interpretAddFunc(0x8108, op_world_map); interpretAddFunc(0x8109, op_inven_cmds); interpretAddFunc(0x810A, op_float_msg); interpretAddFunc(0x810B, op_metarule); interpretAddFunc(0x810C, op_anim); interpretAddFunc(0x810D, op_obj_carrying_pid_obj); interpretAddFunc(0x810E, op_reg_anim_func); interpretAddFunc(0x810F, op_reg_anim_animate); interpretAddFunc(0x8110, op_reg_anim_animate_reverse); interpretAddFunc(0x8111, op_reg_anim_obj_move_to_obj); interpretAddFunc(0x8112, op_reg_anim_obj_run_to_obj); interpretAddFunc(0x8113, op_reg_anim_obj_move_to_tile); interpretAddFunc(0x8114, op_reg_anim_obj_run_to_tile); interpretAddFunc(0x8115, op_play_gmovie); interpretAddFunc(0x8116, op_add_mult_objs_to_inven); interpretAddFunc(0x8117, op_rm_mult_objs_from_inven); interpretAddFunc(0x8118, op_get_month); interpretAddFunc(0x8119, op_get_day); interpretAddFunc(0x811A, op_explosion); interpretAddFunc(0x811B, op_days_since_visited); interpretAddFunc(0x811C, op_gsay_start); interpretAddFunc(0x811D, op_gsay_end); interpretAddFunc(0x811E, op_gsay_reply); interpretAddFunc(0x811F, op_gsay_option); interpretAddFunc(0x8120, op_gsay_message); interpretAddFunc(0x8121, op_giq_option); interpretAddFunc(0x8122, op_poison); interpretAddFunc(0x8123, op_get_poison); interpretAddFunc(0x8124, op_party_add); interpretAddFunc(0x8125, op_party_remove); interpretAddFunc(0x8126, op_reg_anim_animate_forever); interpretAddFunc(0x8127, op_critter_injure); interpretAddFunc(0x8128, op_combat_is_initialized); interpretAddFunc(0x8129, op_gdialog_barter); interpretAddFunc(0x812A, op_difficulty_level); interpretAddFunc(0x812B, op_running_burning_guy); interpretAddFunc(0x812C, op_inven_unwield); interpretAddFunc(0x812D, op_obj_is_locked); interpretAddFunc(0x812E, op_obj_lock); interpretAddFunc(0x812F, op_obj_unlock); interpretAddFunc(0x8131, op_obj_open); interpretAddFunc(0x8130, op_obj_is_open); interpretAddFunc(0x8132, op_obj_close); interpretAddFunc(0x8133, op_game_ui_disable); interpretAddFunc(0x8134, op_game_ui_enable); interpretAddFunc(0x8135, op_game_ui_is_disabled); interpretAddFunc(0x8136, op_gfade_out); interpretAddFunc(0x8137, op_gfade_in); interpretAddFunc(0x8138, op_item_caps_total); interpretAddFunc(0x8139, op_item_caps_adjust); interpretAddFunc(0x813A, op_anim_action_frame); interpretAddFunc(0x813B, op_reg_anim_play_sfx); interpretAddFunc(0x813C, op_critter_mod_skill); interpretAddFunc(0x813D, op_sfx_build_char_name); interpretAddFunc(0x813E, op_sfx_build_ambient_name); interpretAddFunc(0x813F, op_sfx_build_interface_name); interpretAddFunc(0x8140, op_sfx_build_item_name); interpretAddFunc(0x8141, op_sfx_build_weapon_name); interpretAddFunc(0x8142, op_sfx_build_scenery_name); interpretAddFunc(0x8143, op_attack_setup); interpretAddFunc(0x8144, op_destroy_mult_objs); interpretAddFunc(0x8145, op_use_obj_on_obj); interpretAddFunc(0x8146, op_endgame_slideshow); interpretAddFunc(0x8147, op_move_obj_inven_to_obj); interpretAddFunc(0x8148, op_endgame_movie); interpretAddFunc(0x8149, op_obj_art_fid); interpretAddFunc(0x814A, op_art_anim); interpretAddFunc(0x814B, op_party_member_obj); interpretAddFunc(0x814C, op_rotation_to_tile); interpretAddFunc(0x814D, op_jam_lock); interpretAddFunc(0x814E, op_gdialog_set_barter_mod); interpretAddFunc(0x814F, op_combat_difficulty); interpretAddFunc(0x8150, op_obj_on_screen); interpretAddFunc(0x8151, op_critter_is_fleeing); interpretAddFunc(0x8152, op_critter_set_flee_state); interpretAddFunc(0x8153, op_terminate_combat); interpretAddFunc(0x8154, op_debug_msg); interpretAddFunc(0x8155, op_critter_stop_attacking); } // NOTE: Uncollapsed 0x45D878. // // 0x45D878 void updateIntExtra() { } // NOTE: Uncollapsed 0x45D878. // // 0x45D878 void intExtraRemoveProgramReferences(Program* program) { } ================================================ FILE: src/int/support/intextra.h ================================================ #ifndef FALLOUT_INT_SUPPORT_INTEXTRA_H_ #define FALLOUT_INT_SUPPORT_INTEXTRA_H_ #include #include "int/intrpret.h" #include "game/object_types.h" typedef enum ScriptError { SCRIPT_ERROR_NOT_IMPLEMENTED, SCRIPT_ERROR_OBJECT_IS_NULL, SCRIPT_ERROR_CANT_MATCH_PROGRAM_TO_SID, SCRIPT_ERROR_FOLLOWS, SCRIPT_ERROR_COUNT, } ScriptError; void dbg_error(Program* program, const char* name, int error); int correctDeath(Object* critter, int anim, bool a3); void intExtraClose(); void initIntExtra(); void updateIntExtra(); void intExtraRemoveProgramReferences(Program* program); #endif /* FALLOUT_INT_SUPPORT_INTEXTRA_H_ */ ================================================ FILE: src/int/widget.c ================================================ #include "int/widget.h" #include #include #include "int/datafile.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "plib/gnw/rect.h" #include "int/memdbg.h" #include "int/sound.h" #include "plib/gnw/text.h" #include "int/window.h" #include "plib/gnw/gnw.h" #define WIDGET_UPDATE_REGIONS_CAPACITY 32 typedef struct StatusBar { unsigned char* field_0; unsigned char* field_4; int win; int x; int y; int width; int height; int field_1C; int field_20; int field_24; } StatusBar; typedef struct UpdateRegion { int win; int x; int y; unsigned int type; int field_10; void* value; UpdateRegionShowFunc* showFunc; UpdateRegionDrawFunc* drawFunc; } UpdateRegion; typedef struct TextInputRegion { int textRegionId; int isUsed; int field_8; int field_C; int field_10; char* text; int field_18; int field_1C; int btn; TextInputRegionDeleteFunc* deleteFunc; int field_28; void* deleteFuncUserData; } TextInputRegion; typedef struct TextRegion { int win; int isUsed; int x; int y; int width; int height; int textAlignment; int textFlags; int backgroundColor; int font; } TextRegion; static void deleteChar(char* string, int pos, int length); static void insertChar(char* string, char ch, int pos, int length); static void textInputRegionDispatch(int btn, int inputEvent); static void showRegion(UpdateRegion* updateRegion); static void freeStatusBar(); static void drawStatusBar(); // 0x66E6A0 static UpdateRegion* updateRegions[WIDGET_UPDATE_REGIONS_CAPACITY]; // 0x66E720 static StatusBar statusBar; // 0x66E750 static TextInputRegion* textInputRegions; // 0x66E754 static int numTextInputRegions; // 0x66E758 static TextRegion* textRegions; // 0x66E75C static int statusBarActive; // 0x66E760 static int numTextRegions; // 0x4B45A0 static void deleteChar(char* string, int pos, int length) { if (length > pos) { memcpy(string + pos, string + pos + 1, length - pos); } } // 0x4B45C8 static void insertChar(char* string, char ch, int pos, int length) { if (length >= pos) { if (length > pos) { memmove(string + pos + 1, string + pos, length - pos); } string[pos] = ch; } } // 0x4B4788 static void textInputRegionDispatch(int btn, int inputEvent) { // TODO: Incomplete. } // 0x4B51D4 int win_add_text_input_region(int textRegionId, char* text, int a3, int a4) { int textInputRegionIndex; int oldFont; int btn; if (textRegionId <= 0 || textRegionId > numTextRegions) { return 0; } if (textRegions[textRegionId - 1].isUsed == 0) { return 0; } for (textInputRegionIndex = 0; textInputRegionIndex < numTextInputRegions; textInputRegionIndex++) { if (textInputRegions[textInputRegionIndex].isUsed == 0) { break; } } if (textInputRegionIndex == numTextInputRegions) { if (textInputRegions == NULL) { textInputRegions = (TextInputRegion*)mymalloc(sizeof(*textInputRegions), __FILE__, __LINE__); } else { textInputRegions = (TextInputRegion*)myrealloc(textInputRegions, sizeof(*textInputRegions) * (numTextInputRegions + 1), __FILE__, __LINE__); } numTextInputRegions++; } textInputRegions[textInputRegionIndex].field_28 = a4; textInputRegions[textInputRegionIndex].textRegionId = textRegionId; textInputRegions[textInputRegionIndex].isUsed = 1; textInputRegions[textInputRegionIndex].field_8 = a3; textInputRegions[textInputRegionIndex].field_C = 0; textInputRegions[textInputRegionIndex].text = text; textInputRegions[textInputRegionIndex].field_10 = strlen(text); textInputRegions[textInputRegionIndex].deleteFunc = NULL; textInputRegions[textInputRegionIndex].deleteFuncUserData = NULL; oldFont = text_curr(); text_font(textRegions[textRegionId - 1].font); btn = win_register_button(textRegions[textRegionId - 1].win, textRegions[textRegionId - 1].x, textRegions[textRegionId - 1].y, textRegions[textRegionId - 1].width, text_height(), -1, -1, -1, (textInputRegionIndex + 1) | 0x400, NULL, NULL, NULL, 0); win_register_button_func(btn, NULL, NULL, NULL, textInputRegionDispatch); // NOTE: Uninline. win_print_text_region(textRegionId, text); textInputRegions[textInputRegionIndex].btn = btn; text_font(oldFont); return textInputRegionIndex + 1; } // NOTE: Unused. // // 0x4B53A8 void windowSelectTextInputRegion(int textInputRegionId) { textInputRegionDispatch(textInputRegions[textInputRegionId - 1].btn, textInputRegionId | 0x400); } // 0x4B53D0 int win_delete_all_text_input_regions(int win) { int index; for (index = 0; index < numTextInputRegions; index++) { if (textRegions[textInputRegions[index].textRegionId - 1].win == win) { win_delete_text_input_region(index + 1); } } return 1; } // 0x4B541C int win_delete_text_input_region(int textInputRegionId) { int textInputRegionIndex; textInputRegionIndex = textInputRegionId - 1; if (textInputRegionIndex >= 0 && textInputRegionIndex < numTextInputRegions) { if (textInputRegions[textInputRegionIndex].isUsed != 0) { if (textInputRegions[textInputRegionIndex].deleteFunc != NULL) { textInputRegions[textInputRegionIndex].deleteFunc(textInputRegions[textInputRegionIndex].text, textInputRegions[textInputRegionIndex].deleteFuncUserData); } // NOTE: Uninline. win_delete_text_region(textInputRegions[textInputRegionIndex].textRegionId); return 1; } } return 0; } // 0x4B54C8 int win_set_text_input_delete_func(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData) { int textInputRegionIndex; textInputRegionIndex = textInputRegionId - 1; if (textInputRegionIndex >= 0 && textInputRegionIndex < numTextInputRegions) { if (textInputRegions[textInputRegionIndex].isUsed != 0) { textInputRegions[textInputRegionIndex].deleteFunc = deleteFunc; textInputRegions[textInputRegionIndex].deleteFuncUserData = userData; return 1; } } return 0; } // 0x4B5508 int win_add_text_region(int win, int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor) { int textRegionIndex; int oldFont; int height; for (textRegionIndex = 0; textRegionIndex < numTextRegions; textRegionIndex++) { if (textRegions[textRegionIndex].isUsed == 0) { break; } } if (textRegionIndex == numTextRegions) { if (textRegions == NULL) { textRegions = (TextRegion*)mymalloc(sizeof(*textRegions), __FILE__, __LINE__); // "..\int\WIDGET.C", 615 } else { textRegions = (TextRegion*)myrealloc(textRegions, sizeof(*textRegions) * (numTextRegions + 1), __FILE__, __LINE__); // "..\int\WIDGET.C", 616 } numTextRegions++; } oldFont = text_curr(); text_font(font); height = text_height(); text_font(oldFont); if ((textFlags & FONT_SHADOW) != 0) { width++; height++; } textRegions[textRegionIndex].isUsed = 1; textRegions[textRegionIndex].win = win; textRegions[textRegionIndex].x = x; textRegions[textRegionIndex].y = y; textRegions[textRegionIndex].width = width; textRegions[textRegionIndex].height = height; textRegions[textRegionIndex].font = font; textRegions[textRegionIndex].textAlignment = textAlignment; textRegions[textRegionIndex].textFlags = textFlags; textRegions[textRegionIndex].backgroundColor = backgroundColor; return textRegionIndex + 1; } // 0x4B5634 int win_print_text_region(int textRegionId, char* string) { int textRegionIndex; int oldFont; textRegionIndex = textRegionId - 1; if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) { if (textRegions[textRegionIndex].isUsed != 0) { oldFont = text_curr(); text_font(textRegions[textRegionIndex].font); win_fill(textRegions[textRegionIndex].win, textRegions[textRegionIndex].x, textRegions[textRegionIndex].y, textRegions[textRegionIndex].width, textRegions[textRegionIndex].height, textRegions[textRegionIndex].backgroundColor); windowPrintBuf(textRegions[textRegionIndex].win, string, strlen(string), textRegions[textRegionIndex].width, win_height(textRegions[textRegionIndex].win), textRegions[textRegionIndex].x, textRegions[textRegionIndex].y, textRegions[textRegionIndex].textFlags | 0x2000000, textRegions[textRegionIndex].textAlignment); text_font(oldFont); return 1; } } return 0; } // 0x4B5714 int win_print_substr_region(int textRegionId, char* string, int stringLength) { int textRegionIndex; int oldFont; textRegionIndex = textRegionId - 1; if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) { if (textRegions[textRegionIndex].isUsed != 0) { oldFont = text_curr(); text_font(textRegions[textRegionIndex].font); win_fill(textRegions[textRegionIndex].win, textRegions[textRegionIndex].x, textRegions[textRegionIndex].y, textRegions[textRegionIndex].width, textRegions[textRegionIndex].height, textRegions[textRegionIndex].backgroundColor); windowPrintBuf(textRegions[textRegionIndex].win, string, stringLength, textRegions[textRegionIndex].width, win_height(textRegions[textRegionIndex].win), textRegions[textRegionIndex].x, textRegions[textRegionIndex].y, textRegions[textRegionIndex].textFlags | 0x2000000, textRegions[textRegionIndex].textAlignment); text_font(oldFont); return 1; } } return 0; } // 0x4B57E4 int win_update_text_region(int textRegionId) { int textRegionIndex; Rect rect; textRegionIndex = textRegionId - 1; if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) { if (textRegions[textRegionIndex].isUsed != 0) { rect.ulx = textRegions[textRegionIndex].x; rect.uly = textRegions[textRegionIndex].y; rect.lrx = textRegions[textRegionIndex].x + textRegions[textRegionIndex].width; rect.lry = textRegions[textRegionIndex].y + text_height(); win_draw_rect(textRegions[textRegionIndex].win, &rect); return 1; } } return 0; } // 0x4B5864 int win_delete_text_region(int textRegionId) { int textRegionIndex; textRegionIndex = textRegionId - 1; if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) { if (textRegions[textRegionIndex].isUsed != 0) { textRegions[textRegionIndex].isUsed = 0; return 1; } } return 0; } // 0x4B58A0 int win_delete_all_update_regions(int win) { int index; for (index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) { if (updateRegions[index] != NULL) { if (win == updateRegions[index]->win) { myfree(updateRegions[index], __FILE__, __LINE__); // "..\int\WIDGET.C", 722 updateRegions[index] = NULL; } } } return 1; } // 0x4B58E8 int win_text_region_style(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor) { int textRegionIndex; int oldFont; int height; textRegionIndex = textRegionId - 1; if (textRegionIndex >= 0 && textRegionIndex <= numTextRegions) { if (textRegions[textRegionIndex].isUsed != 0) { textRegions[textRegionIndex].font = font; textRegions[textRegionIndex].textAlignment = textAlignment; oldFont = text_curr(); text_font(font); height = text_height(); text_font(oldFont); if ((textRegions[textRegionIndex].textFlags & FONT_SHADOW) == 0 && (textFlags & FONT_SHADOW) != 0) { height++; textRegions[textRegionIndex].width++; } textRegions[textRegionIndex].height = height; textRegions[textRegionIndex].textFlags = textFlags; textRegions[textRegionIndex].backgroundColor = backgroundColor; return 1; } } return 0; } // 0x4B5998 void win_delete_widgets(int win) { int index; win_delete_all_text_input_regions(win); for (index = 0; index < numTextRegions; index++) { if (textRegions[index].win == win) { // NOTE: Uninline. win_delete_text_region(index + 1); } } win_delete_all_update_regions(win); } // 0x4B5A04 int widgetDoInput() { int index; for (index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) { if (updateRegions[index] != NULL) { showRegion(updateRegions[index]); } } return 0; } // 0x4B5A2C int win_center_str(int win, char* string, int y, int a4) { int windowWidth; int stringWidth; windowWidth = win_width(win); stringWidth = text_width(string); win_print(win, string, 0, (windowWidth - stringWidth) / 2, y, a4); return 1; } // 0x4B5A64 static void showRegion(UpdateRegion* updateRegion) { float value; char stringBuffer[80]; switch (updateRegion->type & 0xFF) { case 1: value = (float)(*(int*)updateRegion->value); break; case 2: value = *(float*)updateRegion->value; break; case 4: value = *(float*)updateRegion->value / 65636.0f; break; case 8: win_print(updateRegion->win, (char*)updateRegion->value, 0, updateRegion->x, updateRegion->y, updateRegion->field_10); return; case 0x10: break; default: debug_printf("Invalid input type given to win_register_update\n"); return; } switch (updateRegion->type & 0xFF00) { case 0x100: sprintf(stringBuffer, " %d ", (int)value); break; case 0x200: sprintf(stringBuffer, " %f ", value); break; case 0x400: sprintf(stringBuffer, " %6.2f%% ", value * 100.0f); break; case 0x800: if (updateRegion->showFunc != NULL) { updateRegion->showFunc(updateRegion->value); } return; default: debug_printf("Invalid output type given to win_register_update\n"); return; } win_print(updateRegion->win, stringBuffer, 0, updateRegion->x, updateRegion->y, updateRegion->field_10 | 0x1000000); } // 0x4B5BE8 int draw_widgets() { int index; for (index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) { if (updateRegions[index] != NULL) { if ((updateRegions[index]->type & 0xFF00) == 0x800) { updateRegions[index]->drawFunc(updateRegions[index]->value); } } } return 1; } // 0x4B5C24 int update_widgets() { for (int index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) { if (updateRegions[index] != NULL) { showRegion(updateRegions[index]); } } return 1; } // 0x4B5C4C int win_register_update(int win, int x, int y, UpdateRegionShowFunc* showFunc, UpdateRegionDrawFunc* drawFunc, void* value, unsigned int type, int a8) { int updateRegionIndex; for (updateRegionIndex = 0; updateRegionIndex < WIDGET_UPDATE_REGIONS_CAPACITY; updateRegionIndex++) { if (updateRegions[updateRegionIndex] == NULL) { break; } } if (updateRegionIndex == WIDGET_UPDATE_REGIONS_CAPACITY) { return -1; } updateRegions[updateRegionIndex] = (UpdateRegion*)mymalloc(sizeof(*updateRegions), __FILE__, __LINE__); // "..\int\WIDGET.C", 859 updateRegions[updateRegionIndex]->win = win; updateRegions[updateRegionIndex]->x = x; updateRegions[updateRegionIndex]->y = y; updateRegions[updateRegionIndex]->type = type; updateRegions[updateRegionIndex]->field_10 = a8; updateRegions[updateRegionIndex]->value = value; updateRegions[updateRegionIndex]->showFunc = showFunc; updateRegions[updateRegionIndex]->drawFunc = drawFunc; return updateRegionIndex; } // 0x4B5D0C int win_delete_update_region(int updateRegionIndex) { if (updateRegionIndex >= 0 && updateRegionIndex < WIDGET_UPDATE_REGIONS_CAPACITY) { if (updateRegions[updateRegionIndex] == NULL) { myfree(updateRegions[updateRegionIndex], __FILE__, __LINE__); // "..\int\WIDGET.C", 875 updateRegions[updateRegionIndex] = NULL; return 1; } } return 0; } // 0x4B5D54 void win_do_updateregions() { for (int index = 0; index < WIDGET_UPDATE_REGIONS_CAPACITY; index++) { if (updateRegions[index] != NULL) { showRegion(updateRegions[index]); } } } // 0x4B5D78 static void freeStatusBar() { if (statusBar.field_0 != NULL) { myfree(statusBar.field_0, __FILE__, __LINE__); // "..\int\WIDGET.C", 891 statusBar.field_0 = NULL; } if (statusBar.field_4 != NULL) { myfree(statusBar.field_4, __FILE__, __LINE__); // "..\int\WIDGET.C", 892 statusBar.field_4 = NULL; } memset(&statusBar, 0, sizeof(statusBar)); statusBarActive = 0; } // 0x4B5DE4 void initWidgets() { int updateRegionIndex; for (updateRegionIndex = 0; updateRegionIndex < WIDGET_UPDATE_REGIONS_CAPACITY; updateRegionIndex++) { updateRegions[updateRegionIndex] = NULL; } textRegions = NULL; numTextRegions = 0; textInputRegions = NULL; numTextInputRegions = 0; freeStatusBar(); } // 0x4B5E1C void widgetsClose() { if (textRegions != NULL) { myfree(textRegions, __FILE__, __LINE__); // "..\int\WIDGET.C", 908 } textRegions = NULL; numTextRegions = 0; if (textInputRegions != NULL) { myfree(textInputRegions, __FILE__, __LINE__); // "..\int\WIDGET.C", 912 } textInputRegions = NULL; numTextInputRegions = 0; freeStatusBar(); } // 0x4B5E7C static void drawStatusBar() { Rect rect; unsigned char* dest; if (statusBarActive) { dest = win_get_buf(statusBar.win) + statusBar.y * win_width(statusBar.win) + statusBar.x; buf_to_buf(statusBar.field_0, statusBar.width, statusBar.height, statusBar.width, dest, win_width(statusBar.win)); buf_to_buf(statusBar.field_4, statusBar.field_1C, statusBar.height, statusBar.width, dest, win_width(statusBar.win)); rect.ulx = statusBar.x; rect.uly = statusBar.y; rect.lrx = statusBar.x + statusBar.width; rect.lry = statusBar.y + statusBar.height; win_draw_rect(statusBar.win, &rect); } } // 0x4B5F5C void real_win_set_status_bar(int a1, int a2, int a3) { if (statusBarActive) { statusBar.field_1C = a2; statusBar.field_20 = a2; statusBar.field_24 = a3; drawStatusBar(); } } // 0x4B5F80 void real_win_update_status_bar(float a1, float a2) { if (statusBarActive) { statusBar.field_1C = (int)(a1 * statusBar.width); statusBar.field_20 = (int)(a1 * statusBar.width); statusBar.field_24 = (int)(a2 * statusBar.width); drawStatusBar(); soundContinueAll(); } } // 0x4B5FD4 void real_win_increment_status_bar(float a1) { if (statusBarActive) { statusBar.field_1C = statusBar.field_20 + (int)(a1 * (statusBar.field_24 - statusBar.field_20)); drawStatusBar(); soundContinueAll(); } } // 0x4B6020 void real_win_add_status_bar(int win, int a2, char* a3, char* a4, int x, int y) { int imageWidth1; int imageHeight1; int imageWidth2; int imageHeight2; freeStatusBar(); statusBar.field_0 = loadRawDataFile(a4, &imageWidth1, &imageHeight1); statusBar.field_4 = loadRawDataFile(a3, &imageWidth2, &imageHeight2); if (imageWidth2 == imageWidth1 && imageHeight2 == imageHeight1) { statusBar.x = x; statusBar.y = y; statusBar.width = imageWidth1; statusBar.height = imageHeight1; statusBar.win = win; real_win_set_status_bar(a2, 0, 0); statusBarActive = 1; } else { freeStatusBar(); debug_printf("status bar dimensions not the same\n"); } } // 0x4B60CC void real_win_get_status_info(int a1, int* a2, int* a3, int* a4) { if (statusBarActive) { *a2 = statusBar.field_1C; *a3 = statusBar.field_20; *a4 = statusBar.field_24; } else { *a2 = -1; *a3 = -1; *a4 = -1; } } // 0x4B6100 void real_win_modify_status_info(int a1, int a2, int a3, int a4) { if (statusBarActive) { statusBar.field_1C = a2; statusBar.field_20 = a3; statusBar.field_24 = a4; } } ================================================ FILE: src/int/widget.h ================================================ #ifndef FALLOUT_INT_WIDGET_H_ #define FALLOUT_INT_WIDGET_H_ typedef void(UpdateRegionShowFunc)(void* value); typedef void(UpdateRegionDrawFunc)(void* value); typedef void(TextInputRegionDeleteFunc)(char* text, void* userData); int win_add_text_input_region(int textRegionId, char* text, int a3, int a4); void windowSelectTextInputRegion(int textInputRegionId); int win_delete_all_text_input_regions(int win); int win_delete_text_input_region(int textInputRegionId); int win_set_text_input_delete_func(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData); int win_add_text_region(int win, int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor); int win_print_text_region(int textRegionId, char* string); int win_print_substr_region(int textRegionId, char* string, int stringLength); int win_update_text_region(int textRegionId); int win_delete_text_region(int textRegionId); int win_delete_all_update_regions(int a1); int win_text_region_style(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor); void win_delete_widgets(int win); int widgetDoInput(); int win_center_str(int win, char* string, int y, int a4); int draw_widgets(); int update_widgets(); int win_register_update(int win, int x, int y, UpdateRegionShowFunc* showFunc, UpdateRegionDrawFunc* drawFunc, void* value, unsigned int type, int a8); int win_delete_update_region(int updateRegionIndex); void win_do_updateregions(); void initWidgets(); void widgetsClose(); void real_win_set_status_bar(int a1, int a2, int a3); void real_win_update_status_bar(float a1, float a2); void real_win_increment_status_bar(float a1); void real_win_add_status_bar(int win, int a2, char* a3, char* a4, int x, int y); void real_win_get_status_info(int a1, int* a2, int* a3, int* a4); void real_win_modify_status_info(int a1, int a2, int a3, int a4); #endif /* FALLOUT_INT_WIDGET_H_ */ ================================================ FILE: src/int/window.c ================================================ #include "int/window.h" #include #include #include #include "plib/db/db.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "int/datafile.h" #include "plib/gnw/grbuf.h" #include "game/game.h" #include "int/intlib.h" #include "int/memdbg.h" #include "int/mousemgr.h" #include "int/movie.h" #include "plib/gnw/button.h" #include "plib/gnw/text.h" #include "plib/gnw/svga.h" typedef enum ManagedButtonMouseEvent { MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN, MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP, MANAGED_BUTTON_MOUSE_EVENT_ENTER, MANAGED_BUTTON_MOUSE_EVENT_EXIT, MANAGED_BUTTON_MOUSE_EVENT_COUNT, } ManagedButtonMouseEvent; typedef enum ManagedButtonRightMouseEvent { MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN, MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP, MANAGED_BUTTON_RIGHT_MOUSE_EVENT_COUNT, } ManagedButtonRightMouseEvent; typedef struct ManagedButton { int btn; int width; int height; int x; int y; int flags; int field_18; char name[32]; Program* program; unsigned char* pressed; unsigned char* normal; unsigned char* hover; void* field_4C; void* field_50; int procs[MANAGED_BUTTON_MOUSE_EVENT_COUNT]; int rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_COUNT]; ManagedButtonMouseEventCallback* mouseEventCallback; ManagedButtonMouseEventCallback* rightMouseEventCallback; void* mouseEventCallbackUserData; void* rightMouseEventCallbackUserData; } ManagedButton; typedef struct ManagedWindow { char name[32]; int window; int width; int height; Region** regions; int currentRegionIndex; int regionsLength; int field_38; ManagedButton* buttons; int buttonsLength; int field_44; int field_48; int field_4C; int field_50; float field_54; float field_58; } ManagedWindow; static_assert(sizeof(ManagedButton) == 0x7C, "wrong size"); static bool checkRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent); static bool checkAllRegions(); static void doRegionRightFunc(Region* region, int a2); static void doRegionFunc(Region* region, int a2); static void doButtonOn(int btn, int keyCode); static void doButtonProc(int btn, int mouseEvent); static void doButtonOff(int btn, int keyCode); static void doButtonPress(int btn, int keyCode); static void doButtonRelease(int btn, int keyCode); static void doRightButtonPress(int btn, int keyCode); static void doRightButtonProc(int btn, int mouseEvent); static void doRightButtonRelease(int btn, int keyCode); static void setButtonGFX(int width, int height, unsigned char* normal, unsigned char* pressed, unsigned char* a5); static void redrawButton(ManagedButton* button); static void removeProgramReferences(Program* program); // 0x51DCAC static int holdTime = 250; // 0x51DCB0 static int checkRegionEnable = 1; // 0x51DCB4 static int winTOS = -1; // 051DCB8 static int currentWindow = -1; // 0x51DCBC static VideoSystemInitProc* gfx_init[12] = { init_mode_320_200, init_mode_640_480, init_mode_640_480_16, init_mode_320_400, init_mode_640_480_16, init_mode_640_400, init_mode_640_480_16, init_mode_800_600, init_mode_640_480_16, init_mode_1024_768, init_mode_640_480_16, init_mode_1280_1024, }; // 0x51DD1C static Size sizes[12] = { { 320, 200 }, { 640, 480 }, { 640, 240 }, { 320, 400 }, { 640, 200 }, { 640, 400 }, { 800, 300 }, { 800, 600 }, { 1024, 384 }, { 1024, 768 }, { 1280, 512 }, { 1280, 1024 }, }; // 0x51DD7C static int numInputFunc = 0; // 0x51DD80 int _lastWin = -1; // 0x51DD84 int _said_quit = 1; // 0x66E770 static int winStack[MANAGED_WINDOW_COUNT]; // 0x66E7B0 static char alphaBlendTable[64 * 256]; // 0x6727B0 static ManagedWindow windows[MANAGED_WINDOW_COUNT]; // 0x672D70 static WindowInputHandler** inputFunc; // 0x672D74 static ManagedWindowCreateCallback* createWindowFunc; // 0x672D78 static ManagedWindowSelectFunc* selectWindowFunc; // 0x672D7C static int xres; // 0x672D80 static DisplayInWindowCallback* displayFunc; // 0x672D84 static WindowDeleteCallback* deleteWindowFunc; // 0x672D88 static int yres; // Highlight color (maybe r). // // 0x672D8C static int currentHighlightColorR; // 0x672D90 static int currentFont; // 0x672D94 static ButtonCallback* soundDisableFunc; // 0x672D98 static ButtonCallback* soundPressFunc; // 0x672D9C static ButtonCallback* soundReleaseFunc; // Text color (maybe g). // // 0x672DA0 static int currentTextColorG; // text color (maybe b). // // 0x672DA4 static int currentTextColorB; // 0x672DA8 static int currentTextFlags; // Text color (maybe r) // // 0x672DAC static int currentTextColorR; // highlight color (maybe g) // // 0x672DB0 static int currentHighlightColorG; // Highlight color (maybe b). // // 0x672DB4 static int currentHighlightColorB; // 0x4B6120 int windowGetFont() { return currentFont; } // 0x4B6128 int windowSetFont(int a1) { currentFont = a1; text_font(a1); return 1; } // NOTE: Unused. // // 0x4B6138 void windowResetTextAttributes() { // NOTE: Uninline. windowSetTextColor(1.0, 1.0, 1.0); // NOTE: Uninline. windowSetTextFlags(0x2000000 | 0x10000); } // 0x4B6160 int windowGetTextFlags() { return currentTextFlags; } // 0x4B6168 int windowSetTextFlags(int a1) { currentTextFlags = a1; return 1; } // 0x4B6174 unsigned char windowGetTextColor() { return colorTable[currentTextColorB | (currentTextColorG << 5) | (currentTextColorR << 10)]; } // 0x4B6198 unsigned char windowGetHighlightColor() { return colorTable[currentHighlightColorB | (currentHighlightColorG << 5) | (currentHighlightColorR << 10)]; } // 0x4B61BC int windowSetTextColor(float r, float g, float b) { currentTextColorR = (int)(r * 31.0); currentTextColorG = (int)(g * 31.0); currentTextColorB = (int)(b * 31.0); return 1; } // 0x4B6208 int windowSetHighlightColor(float r, float g, float b) { currentHighlightColorR = (int)(r * 31.0); currentHighlightColorG = (int)(g * 31.0); currentHighlightColorB = (int)(b * 31.0); return 1; } // 0x4B62E4 static bool checkRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent) { // TODO: Incomplete. return false; } // 0x4B6858 bool windowCheckRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent) { bool rc = checkRegion(windowIndex, mouseX, mouseY, mouseEvent); ManagedWindow* managedWindow = &(windows[windowIndex]); int v1 = managedWindow->field_38; for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (region->field_6C != 0) { region->field_6C = 0; rc = true; if (region->mouseEventCallback != NULL) { region->mouseEventCallback(region, region->mouseEventCallbackUserData, 2); if (v1 != managedWindow->field_38) { return true; } } if (region->rightMouseEventCallback != NULL) { region->rightMouseEventCallback(region, region->rightMouseEventCallbackUserData, 2); if (v1 != managedWindow->field_38) { return true; } } if (region->program != NULL && region->procs[2] != 0) { executeProc(region->program, region->procs[2]); if (v1 != managedWindow->field_38) { return true; } } } } } return rc; } // 0x4B69BC bool windowRefreshRegions() { int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int win = win_get_top_win(mouseX, mouseY); for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) { ManagedWindow* managedWindow = &(windows[windowIndex]); if (managedWindow->window == win) { for (int regionIndex = 0; regionIndex < managedWindow->regionsLength; regionIndex++) { Region* region = managedWindow->regions[regionIndex]; region->rightProcs[3] = 0; } int mouseEvent = mouse_get_buttons(); return windowCheckRegion(windowIndex, mouseX, mouseY, mouseEvent); } } return false; } // 0x4B6A54 static bool checkAllRegions() { if (!checkRegionEnable) { return false; } int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); int mouseEvent = mouse_get_buttons(); int win = win_get_top_win(mouseX, mouseY); for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) { ManagedWindow* managedWindow = &(windows[windowIndex]); if (managedWindow->window != -1 && managedWindow->window == win) { if (_lastWin != -1 && _lastWin != windowIndex && windows[_lastWin].window != -1) { ManagedWindow* managedWindow = &(windows[_lastWin]); int v1 = managedWindow->field_38; for (int regionIndex = 0; regionIndex < managedWindow->regionsLength; regionIndex++) { Region* region = managedWindow->regions[regionIndex]; if (region != NULL && region->rightProcs[3] != 0) { region->rightProcs[3] = 0; if (region->mouseEventCallback != NULL) { region->mouseEventCallback(region, region->mouseEventCallbackUserData, 3); if (v1 != managedWindow->field_38) { return true; } } if (region->rightMouseEventCallback != NULL) { region->rightMouseEventCallback(region, region->rightMouseEventCallbackUserData, 3); if (v1 != managedWindow->field_38) { return true; } } if (region->program != NULL && region->procs[3] != 0) { executeProc(region->program, region->procs[3]); if (v1 != managedWindow->field_38) { return 1; } } } } _lastWin = -1; } else { _lastWin = windowIndex; } return windowCheckRegion(windowIndex, mouseX, mouseY, mouseEvent); } } return false; } // 0x4B6C48 void windowAddInputFunc(WindowInputHandler* handler) { int index; for (index = 0; index < numInputFunc; index++) { if (inputFunc[index] == NULL) { break; } } if (index == numInputFunc) { if (inputFunc != NULL) { inputFunc = (WindowInputHandler**)myrealloc(inputFunc, sizeof(*inputFunc) * (numInputFunc + 1), __FILE__, __LINE__); // "..\\int\\WINDOW.C", 521 } else { inputFunc = (WindowInputHandler**)mymalloc(sizeof(*inputFunc), __FILE__, __LINE__); // "..\\int\\WINDOW.C", 523 } } inputFunc[numInputFunc] = handler; numInputFunc++; } // 0x4B6CE8 static void doRegionRightFunc(Region* region, int a2) { int v1 = windows[currentWindow].field_38; if (region->rightMouseEventCallback != NULL) { region->rightMouseEventCallback(region, region->rightMouseEventCallbackUserData, a2); if (v1 != windows[currentWindow].field_38) { return; } } if (a2 < 4) { if (region->program != NULL && region->rightProcs[a2] != 0) { executeProc(region->program, region->rightProcs[a2]); } } } // 0x4B6D68 static void doRegionFunc(Region* region, int a2) { int v1 = windows[currentWindow].field_38; if (region->mouseEventCallback != NULL) { region->mouseEventCallback(region, region->mouseEventCallbackUserData, a2); if (v1 != windows[currentWindow].field_38) { return; } } if (a2 < 4) { if (region->program != NULL && region->rightProcs[a2] != 0) { executeProc(region->program, region->rightProcs[a2]); } } } // 0x4B6DE8 bool windowActivateRegion(const char* regionName, int a2) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (a2 <= 4) { for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (stricmp(regionGetName(region), regionName) == 0) { doRegionFunc(region, a2); return true; } } } else { for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (stricmp(regionGetName(region), regionName) == 0) { doRegionRightFunc(region, a2 - 5); return true; } } } return false; } // 0x4B6ED0 int getInput() { int keyCode = get_input(); if (keyCode == KEY_CTRL_Q || keyCode == KEY_CTRL_X || keyCode == KEY_F10) { game_quit_with_confirm(); } if (game_user_wants_to_quit != 0) { _said_quit = 1 - _said_quit; if (_said_quit) { return -1; } return KEY_ESCAPE; } for (int index = 0; index < numInputFunc; index++) { WindowInputHandler* handler = inputFunc[index]; if (handler != NULL) { if (handler(keyCode) != 0) { return -1; } } } return keyCode; } // 0x4B6F60 static void doButtonOn(int btn, int keyCode) { doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_ENTER); } // 0x4B6F68 static void doButtonProc(int btn, int mouseEvent) { int win = win_last_button_winID(); if (win == -1) { return; } for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) { ManagedWindow* managedWindow = &(windows[windowIndex]); if (managedWindow->window == win) { for (int buttonIndex = 0; buttonIndex < managedWindow->buttonsLength; buttonIndex++) { ManagedButton* managedButton = &(managedWindow->buttons[buttonIndex]); if (managedButton->btn == btn) { if ((managedButton->flags & 0x02) != 0) { win_set_button_rest_state(managedButton->btn, 0, 0); } else { if (managedButton->program != NULL && managedButton->procs[mouseEvent] != 0) { executeProc(managedButton->program, managedButton->procs[mouseEvent]); } if (managedButton->mouseEventCallback != NULL) { managedButton->mouseEventCallback(managedButton->mouseEventCallbackUserData, mouseEvent); } } } } } } } // 0x4B7028 static void doButtonOff(int btn, int keyCode) { doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_EXIT); } // 0x4B7034 static void doButtonPress(int btn, int keyCode) { doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN); } // 0x4B703C static void doButtonRelease(int btn, int keyCode) { doButtonProc(btn, MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP); } // NOTE: Unused. // // 0x4B7048 static void doRightButtonPress(int btn, int keyCode) { doRightButtonProc(btn, MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN); } // NOTE: Unused. // // 0x4B704C static void doRightButtonProc(int btn, int mouseEvent) { int win = win_last_button_winID(); if (win == -1) { return; } for (int windowIndex = 0; windowIndex < MANAGED_WINDOW_COUNT; windowIndex++) { ManagedWindow* managedWindow = &(windows[windowIndex]); if (managedWindow->window == win) { for (int buttonIndex = 0; buttonIndex < managedWindow->buttonsLength; buttonIndex++) { ManagedButton* managedButton = &(managedWindow->buttons[buttonIndex]); if (managedButton->btn == btn) { if ((managedButton->flags & 0x02) != 0) { win_set_button_rest_state(managedButton->btn, 0, 0); } else { if (managedButton->program != NULL && managedButton->rightProcs[mouseEvent] != 0) { executeProc(managedButton->program, managedButton->rightProcs[mouseEvent]); } if (managedButton->rightMouseEventCallback != NULL) { managedButton->rightMouseEventCallback(managedButton->rightMouseEventCallbackUserData, mouseEvent); } } } } } } } // NOTE: Unused. // // 0x4B710C static void doRightButtonRelease(int btn, int keyCode) { doRightButtonProc(btn, MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP); } // 0x4B7118 static void setButtonGFX(int width, int height, unsigned char* normal, unsigned char* pressed, unsigned char* a5) { if (normal != NULL) { buf_fill(normal, width, height, width, colorTable[0]); buf_fill(normal + width + 1, width - 2, height - 2, width, intensityColorTable[colorTable[32767]][89]); draw_line(normal, width, 1, 1, width - 2, 1, colorTable[32767]); draw_line(normal, width, 2, 2, width - 3, 2, colorTable[32767]); draw_line(normal, width, 1, height - 2, width - 2, height - 2, intensityColorTable[colorTable[32767]][44]); draw_line(normal, width, 2, height - 3, width - 3, height - 3, intensityColorTable[colorTable[32767]][44]); draw_line(normal, width, width - 2, 1, width - 3, 2, intensityColorTable[colorTable[32767]][89]); draw_line(normal, width, 1, 2, 1, height - 3, colorTable[32767]); draw_line(normal, width, 2, 3, 2, height - 4, colorTable[32767]); draw_line(normal, width, width - 2, 2, width - 2, height - 3, intensityColorTable[colorTable[32767]][44]); draw_line(normal, width, width - 3, 3, width - 3, height - 4, intensityColorTable[colorTable[32767]][44]); draw_line(normal, width, 1, height - 2, 2, height - 3, intensityColorTable[colorTable[32767]][89]); } if (pressed != NULL) { buf_fill(pressed, width, height, width, colorTable[0]); buf_fill(pressed + width + 1, width - 2, height - 2, width, intensityColorTable[colorTable[32767]][89]); draw_line(pressed, width, 1, 1, width - 2, 1, colorTable[32767] + 44); draw_line(pressed, width, 1, 1, 1, height - 2, colorTable[32767] + 44); } if (a5 != NULL) { buf_fill(a5, width, height, width, colorTable[0]); buf_fill(a5 + width + 1, width - 2, height - 2, width, intensityColorTable[colorTable[32767]][89]); draw_line(a5, width, 1, 1, width - 2, 1, colorTable[32767]); draw_line(a5, width, 2, 2, width - 3, 2, colorTable[32767]); draw_line(a5, width, 1, height - 2, width - 2, height - 2, intensityColorTable[colorTable[32767]][44]); draw_line(a5, width, 2, height - 3, width - 3, height - 3, intensityColorTable[colorTable[32767]][44]); draw_line(a5, width, width - 2, 1, width - 3, 2, intensityColorTable[colorTable[32767]][89]); draw_line(a5, width, 1, 2, 1, height - 3, colorTable[32767]); draw_line(a5, width, 2, 3, 2, height - 4, colorTable[32767]); draw_line(a5, width, width - 2, 2, width - 2, height - 3, intensityColorTable[colorTable[32767]][44]); draw_line(a5, width, width - 3, 3, width - 3, height - 4, intensityColorTable[colorTable[32767]][44]); draw_line(a5, width, 1, height - 2, 2, height - 3, intensityColorTable[colorTable[32767]][89]); } } // NOTE: Unused. // // 0x4B75F4 static void redrawButton(ManagedButton* button) { win_register_button_image(button->btn, button->normal, button->pressed, button->hover, 0); } // NOTE: Unused. // // 0x4B7610 int windowHide() { if (windows[currentWindow].window == -1) { return 0; } win_hide(windows[currentWindow].window); return 1; } // NOTE: Unused. // // 0x4B7648 int windowShow() { if (windows[currentWindow].window == -1) { return 0; } win_show(windows[currentWindow].window); return 1; } // 0x4B7680 int windowDraw() { ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->window == -1) { return 0; } win_draw(managedWindow->window); return 1; } // NOTE: Unused. // // 0x4B76B8 int windowDrawRect(int left, int top, int right, int bottom) { Rect rect; rect.ulx = left; rect.uly = top; rect.lrx = right; rect.lry = bottom; win_draw_rect(windows[currentWindow].window, &rect); return 1; } // NOTE: Unused. // // 0x4B76F8 int windowDrawRectID(int windowId, int left, int top, int right, int bottom) { Rect rect; rect.ulx = left; rect.uly = top; rect.lrx = right; rect.lry = bottom; win_draw_rect(windows[windowId].window, &rect); return 1; } // 0x4B7734 int windowWidth() { return windows[currentWindow].width; } // 0x4B7754 int windowHeight() { return windows[currentWindow].height; } // NOTE: Unused. // // 0x4B7774 int windowSX() { Rect rect; win_get_rect(windows[currentWindow].window, &rect); return rect.ulx; } // NOTE: Unused. // // 0x4B77A4 int windowSY() { Rect rect; win_get_rect(windows[currentWindow].window, &rect); return rect.uly; } // NOTE: Unused. // // 0x4B77D4 int pointInWindow(int x, int y) { Rect rect; win_get_rect(windows[currentWindow].window, &rect); return x >= rect.ulx && x <= rect.lrx && y >= rect.uly && y <= rect.lry; } // NOTE: Unused. // // 0x4B7828 int windowGetRect(Rect* rect) { return win_get_rect(windows[currentWindow].window, rect); } // NOTE: Unused. // // 0x4B7854 int windowGetID() { return currentWindow; } // NOTE: Inlined. // // 0x4B785C int windowGetGNWID() { return windows[currentWindow].window; } // NOTE: Unused. // // 0x4B787C int windowGetSpecificGNWID(int windowIndex) { if (windowIndex >= 0 && windowIndex < MANAGED_WINDOW_COUNT) { return windows[windowIndex].window; } return -1; } // 0x4B78A4 bool deleteWindow(const char* windowName) { int index; for (index = 0; index < MANAGED_WINDOW_COUNT; index++) { ManagedWindow* managedWindow = &(windows[index]); if (stricmp(managedWindow->name, windowName) == 0) { break; } } if (index == MANAGED_WINDOW_COUNT) { return false; } if (deleteWindowFunc != NULL) { deleteWindowFunc(index, windowName); } ManagedWindow* managedWindow = &(windows[index]); win_delete_widgets(managedWindow->window); win_delete(managedWindow->window); managedWindow->window = -1; managedWindow->name[0] = '\0'; if (managedWindow->buttons != NULL) { for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* button = &(managedWindow->buttons[index]); if (button->hover != NULL) { myfree(button->hover, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 802 } if (button->field_4C != NULL) { myfree(button->field_4C, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 804 } if (button->pressed != NULL) { myfree(button->pressed, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 806 } if (button->normal != NULL) { myfree(button->normal, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 808 } } myfree(managedWindow->buttons, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 810 } if (managedWindow->regions != NULL) { for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { regionDelete(region); } } myfree(managedWindow->regions, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 818 managedWindow->regions = NULL; } return true; } // 0x4B7AC4 int resizeWindow(const char* windowName, int x, int y, int width, int height) { // TODO: Incomplete. return -1; } // 0x4B7E7C int scaleWindow(const char* windowName, int x, int y, int width, int height) { // TODO: Incomplete. return -1; } // 0x4B7F3C int createWindow(const char* windowName, int x, int y, int width, int height, int a6, int flags) { int windowIndex = -1; // NOTE: Original code is slightly different. for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) { ManagedWindow* managedWindow = &(windows[index]); if (managedWindow->window == -1) { windowIndex = index; break; } else { if (stricmp(managedWindow->name, windowName) == 0) { deleteWindow(windowName); windowIndex = index; break; } } } if (windowIndex == -1) { return -1; } ManagedWindow* managedWindow = &(windows[windowIndex]); strncpy(managedWindow->name, windowName, 32); managedWindow->field_54 = 1.0; managedWindow->field_58 = 1.0; managedWindow->field_38 = 0; managedWindow->regions = NULL; managedWindow->regionsLength = 0; managedWindow->width = width; managedWindow->height = height; managedWindow->buttons = NULL; managedWindow->buttonsLength = 0; flags |= 0x101; if (createWindowFunc != NULL) { createWindowFunc(windowIndex, managedWindow->name, &flags); } managedWindow->window = win_add(x, y, width, height, a6, flags); managedWindow->field_48 = 0; managedWindow->field_44 = 0; managedWindow->field_4C = a6; managedWindow->field_50 = flags; return windowIndex; } // 0x4B80A4 int windowOutput(char* string) { if (currentWindow == -1) { return 0; } ManagedWindow* managedWindow = &(windows[currentWindow]); int x = (int)(managedWindow->field_44 * managedWindow->field_54); int y = (int)(managedWindow->field_48 * managedWindow->field_58); // NOTE: Uses `add` at 0x4B810E, not bitwise `or`. int flags = windowGetTextColor() + windowGetTextFlags(); win_print(managedWindow->window, string, 0, x, y, flags); return 1; } // 0x4B814C bool windowGotoXY(int x, int y) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); managedWindow->field_44 = (int)(x * managedWindow->field_54); managedWindow->field_48 = (int)(y * managedWindow->field_58); return true; } // 0x4B81C4 bool selectWindowID(int index) { if (index < 0 || index >= MANAGED_WINDOW_COUNT) { return false; } ManagedWindow* managedWindow = &(windows[index]); if (managedWindow->window == -1) { return false; } currentWindow = index; if (selectWindowFunc != NULL) { selectWindowFunc(index, managedWindow->name); } return true; } // 0x4B821C int selectWindow(const char* windowName) { if (currentWindow != -1) { ManagedWindow* managedWindow = &(windows[currentWindow]); if (stricmp(managedWindow->name, windowName) == 0) { return currentWindow; } } int index; for (index = 0; index < MANAGED_WINDOW_COUNT; index++) { ManagedWindow* managedWindow = &(windows[index]); if (managedWindow->window != -1) { if (stricmp(managedWindow->name, windowName) == 0) { break; } } } if (selectWindowID(index)) { return index; } return -1; } // NOTE: Unused. // // 0x4B82A0 int windowGetDefined(const char* name) { int index; for (index = 0; index < MANAGED_WINDOW_COUNT; index++) { if (windows[index].window != -1 && stricmp(windows[index].name, name) == 0) { return 1; } } return 0; } // 0x4B82DC unsigned char* windowGetBuffer() { if (currentWindow != -1) { ManagedWindow* managedWindow = &(windows[currentWindow]); return win_get_buf(managedWindow->window); } return NULL; } // NOTE: Unused. // // 0x4B8308 char* windowGetName() { if (currentWindow != -1) { return windows[currentWindow].name; } return NULL; } // 0x4B8330 int pushWindow(const char* windowName) { if (winTOS >= MANAGED_WINDOW_COUNT) { return -1; } int oldCurrentWindowIndex = currentWindow; int windowIndex = selectWindow(windowName); if (windowIndex == -1) { return -1; } // TODO: Check. for (int index = 0; index < winTOS; index++) { if (winStack[index] == oldCurrentWindowIndex) { memcpy(&(winStack[index]), &(winStack[index + 1]), sizeof(*winStack) * (winTOS - index)); break; } } winTOS++; winStack[winTOS] = oldCurrentWindowIndex; return windowIndex; } // 0x4B83D4 int popWindow() { if (winTOS == -1) { return -1; } int windowIndex = winStack[winTOS]; ManagedWindow* managedWindow = &(windows[windowIndex]); winTOS--; return selectWindow(managedWindow->name); } // 0x4B8414 void windowPrintBuf(int win, char* string, int stringLength, int width, int maxY, int x, int y, int flags, int textAlignment) { if (y + text_height() > maxY) { return; } if (stringLength > 255) { stringLength = 255; } char* stringCopy = (char*)mymalloc(stringLength + 1, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1078 strncpy(stringCopy, string, stringLength); stringCopy[stringLength] = '\0'; int stringWidth = text_width(stringCopy); int stringHeight = text_height(); if (stringWidth == 0 || stringHeight == 0) { myfree(stringCopy, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1085 return; } if ((flags & FONT_SHADOW) != 0) { stringWidth++; stringHeight++; } unsigned char* backgroundBuffer = (unsigned char*)mycalloc(stringWidth, stringHeight, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1093 unsigned char* backgroundBufferPtr = backgroundBuffer; text_to_buf(backgroundBuffer, stringCopy, stringWidth, stringWidth, flags); switch (textAlignment) { case TEXT_ALIGNMENT_LEFT: if (stringWidth < width) { width = stringWidth; } break; case TEXT_ALIGNMENT_RIGHT: if (stringWidth <= width) { x += (width - stringWidth); width = stringWidth; } else { backgroundBufferPtr = backgroundBuffer + stringWidth - width; } break; case TEXT_ALIGNMENT_CENTER: if (stringWidth <= width) { x += (width - stringWidth) / 2; width = stringWidth; } else { backgroundBufferPtr = backgroundBuffer + (stringWidth - width) / 2; } break; } if (stringHeight + y > win_height(win)) { stringHeight = win_height(win) - y; } if ((flags & 0x2000000) != 0) { trans_buf_to_buf(backgroundBufferPtr, width, stringHeight, stringWidth, win_get_buf(win) + win_width(win) * y + x, win_width(win)); } else { buf_to_buf(backgroundBufferPtr, width, stringHeight, stringWidth, win_get_buf(win) + win_width(win) * y + x, win_width(win)); } myfree(backgroundBuffer, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1130 myfree(stringCopy, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1131 } // 0x4B8638 char** windowWordWrap(char* string, int maxLength, int a3, int* substringListLengthPtr) { if (string == NULL) { *substringListLengthPtr = 0; return NULL; } char** substringList = NULL; int substringListLength = 0; char* start = string; char* pch = string; int v1 = a3; while (*pch != '\0') { v1 += text_char_width(*pch & 0xFF); if (*pch != '\n' && v1 <= maxLength) { v1 += text_spacing(); pch++; } else { while (v1 > maxLength) { v1 -= text_char_width(*pch); pch--; } if (*pch != '\n') { while (pch != start && *pch != ' ') { pch--; } } if (substringList != NULL) { substringList = (char**)myrealloc(substringList, sizeof(*substringList) * (substringListLength + 1), __FILE__, __LINE__); // "..\int\WINDOW.C", 1166 } else { substringList = (char**)mymalloc(sizeof(*substringList), __FILE__, __LINE__); // "..\int\WINDOW.C", 1167 } char* substring = (char*)mymalloc(pch - start + 1, __FILE__, __LINE__); // "..\int\WINDOW.C", 1169 strncpy(substring, start, pch - start); substring[pch - start] = '\0'; substringList[substringListLength] = substring; while (*pch == ' ') { pch++; } v1 = 0; start = pch; substringListLength++; } } if (start != pch) { if (substringList != NULL) { substringList = (char**)myrealloc(substringList, sizeof(*substringList) * (substringListLength + 1), __FILE__, __LINE__); // "..\int\WINDOW.C", 1184 } else { substringList = (char**)mymalloc(sizeof(*substringList), __FILE__, __LINE__); // "..\int\WINDOW.C", 1185 } char* substring = (char*)mymalloc(pch - start + 1, __FILE__, __LINE__); // "..\int\WINDOW.C", 1169 strncpy(substring, start, pch - start); substring[pch - start] = '\0'; substringList[substringListLength] = substring; substringListLength++; } *substringListLengthPtr = substringListLength; return substringList; } // 0x4B880C void windowFreeWordList(char** substringList, int substringListLength) { if (substringList == NULL) { return; } for (int index = 0; index < substringListLength; index++) { myfree(substringList[index], __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1200 } myfree(substringList, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1201 } // Renders multiline string in the specified bounding box. // // 0x4B8854 void windowWrapLineWithSpacing(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment, int a9) { if (string == NULL) { return; } int substringListLength; char** substringList = windowWordWrap(string, width, 0, &substringListLength); for (int index = 0; index < substringListLength; index++) { int v1 = y + index * (a9 + text_height()); windowPrintBuf(win, substringList[index], strlen(substringList[index]), width, height + y, x, v1, flags, textAlignment); } windowFreeWordList(substringList, substringListLength); } // Renders multiline string in the specified bounding box. // // 0x4B88FC void windowWrapLine(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment) { windowWrapLineWithSpacing(win, string, width, height, x, y, flags, textAlignment, 0); } // 0x4B8920 bool windowPrintRect(char* string, int a2, int textAlignment) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); int width = (int)(a2 * managedWindow->field_54); int height = win_height(managedWindow->window); int x = managedWindow->field_44; int y = managedWindow->field_48; int flags = windowGetTextColor() | 0x2000000; // NOTE: Uninline. windowWrapLine(managedWindow->window, string, width, height, x, y, flags, textAlignment); return true; } // 0x4B89B0 bool windowFormatMessage(char* string, int x, int y, int width, int height, int textAlignment) { ManagedWindow* managedWindow = &(windows[currentWindow]); int flags = windowGetTextColor() | 0x2000000; // NOTE: Uninline. windowWrapLine(managedWindow->window, string, width, height, x, y, flags, textAlignment); return true; } // NOTE: Unused. // // 0x4B8A14 int windowFormatMessageColor(char* string, int x, int y, int width, int height, int textAlignment, int flags) { windowWrapLine(windows[currentWindow].window, string, width, height, x, y, flags, textAlignment); return 1; } // 0x4B8A60 bool windowPrint(char* string, int a2, int x, int y, int a5) { ManagedWindow* managedWindow = &(windows[currentWindow]); x = (int)(x * managedWindow->field_54); y = (int)(y * managedWindow->field_58); win_print(managedWindow->window, string, a2, x, y, a5); return true; } // NOTE: Unused. // // 0x4B8ADC int windowPrintFont(char* string, int a2, int x, int y, int a5, int font) { int oldFont; oldFont = text_curr(); text_font(font); windowPrint(string, a2, x, y, a5); text_font(oldFont); return 1; } // 0x4B8B10 void displayInWindow(unsigned char* data, int width, int height, int pitch) { if (displayFunc != NULL) { // NOTE: The second parameter is unclear as there is no distinction // between address of entire window struct and it's name (since it's the // first field). I bet on name since it matches WindowDeleteCallback, // which accepts window index and window name as seen at 0x4B7927). displayFunc(currentWindow, windows[currentWindow].name, data, width, height); } if (width == pitch) { // NOTE: Uninline. if (pitch == windowWidth() && height == windowHeight()) { // NOTE: Uninline. unsigned char* windowBuffer = windowGetBuffer(); memcpy(windowBuffer, data, height * width); } else { // NOTE: Uninline. unsigned char* windowBuffer = windowGetBuffer(); drawScaledBuf(windowBuffer, windowWidth(), windowHeight(), data, width, height); } } else { // NOTE: Uninline. unsigned char* windowBuffer = windowGetBuffer(); drawScaled(windowBuffer, windowWidth(), windowHeight(), windowWidth(), data, width, height, pitch); } } // 0x4B8C68 void displayFile(char* fileName) { int width; int height; unsigned char* data = loadDataFile(fileName, &width, &height); if (data != NULL) { displayInWindow(data, width, height, width); myfree(data, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1294 } } // 0x4B8CA8 void displayFileRaw(char* fileName) { int width; int height; unsigned char* data = loadRawDataFile(fileName, &width, &height); if (data != NULL) { displayInWindow(data, width, height, width); myfree(data, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1305 } } // NOTE: Unused. // // 0x4B8E0C int windowDisplayRaw(char* fileName) { int imageWidth; int imageHeight; unsigned char* imageData; imageData = loadDataFile(fileName, &imageWidth, &imageHeight); if (imageData == NULL) { return 0; } displayInWindow(imageData, imageWidth, imageHeight, imageWidth); myfree(imageData, __FILE__, __LINE__); // "..\int\WINDOW.C", 1363 return 1; } // 0x4B8E50 bool windowDisplay(char* fileName, int x, int y, int width, int height) { int imageWidth; int imageHeight; unsigned char* imageData = loadDataFile(fileName, &imageWidth, &imageHeight); if (imageData == NULL) { return false; } windowDisplayBuf(imageData, imageWidth, imageHeight, x, y, width, height); myfree(imageData, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1376 return true; } // NOTE: Unused // // 0x4B8EA0 int windowDisplayScaled(char* fileName, int x, int y, int width, int height) { int imageWidth; int imageHeight; unsigned char* imageData; imageData = loadDataFile(fileName, &imageWidth, &imageHeight); if (imageData == NULL) { return 0; } windowDisplayBufScaled(imageData, imageWidth, imageHeight, x, y, width, height); myfree(imageData, __FILE__, __LINE__); // "..\int\WINDOW.C", 1389 return 1; } // 0x4B8EF0 bool windowDisplayBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight) { ManagedWindow* managedWindow = &(windows[currentWindow]); unsigned char* windowBuffer = win_get_buf(managedWindow->window); buf_to_buf(src, destWidth, destHeight, srcWidth, windowBuffer + managedWindow->width * destY + destX, managedWindow->width); return true; } // NOTE: Unused. // // 0x4B8F64 int windowDisplayTransBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight) { unsigned char* windowBuffer; windowBuffer = win_get_buf(windows[currentWindow].window); trans_buf_to_buf(src, destWidth, destHeight, srcWidth, windowBuffer + destY * windows[currentWindow].width + destX, windows[currentWindow].width); return 1; } // NOTE: Unused. // // 0x4B8FD8 int windowDisplayBufScaled(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight) { unsigned char* windowBuffer; windowBuffer = win_get_buf(windows[currentWindow].window); drawScaled(windowBuffer + destY * windows[currentWindow].width + destX, destWidth, destHeight, windows[currentWindow].width, src, srcWidth, srcHeight, srcWidth); return 1; } // 0x4B9048 int windowGetXres() { return xres; } // 0x4B9050 int windowGetYres() { return yres; } // 0x4B9058 static void removeProgramReferences(Program* program) { for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) { ManagedWindow* managedWindow = &(windows[index]); if (managedWindow->window != -1) { for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (program == managedButton->program) { managedButton->program = NULL; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_ENTER] = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_EXIT] = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN] = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP] = 0; } } for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (program == region->program) { region->program = NULL; region->procs[1] = 0; region->procs[0] = 0; region->procs[3] = 0; region->procs[2] = 0; } } } } } } // 0x4B9190 void initWindow(int resolution, int a2) { char err[MAX_PATH]; int rc; int i, j; interpretRegisterProgramDeleteCallback(removeProgramReferences); currentTextColorR = 0; currentTextColorG = 0; currentTextColorB = 0; currentHighlightColorR = 0; currentHighlightColorG = 0; currentTextFlags = 0x2010000; yres = sizes[resolution].height; // screen height currentHighlightColorB = 0; xres = sizes[resolution].width; // screen width for (int i = 0; i < MANAGED_WINDOW_COUNT; i++) { windows[i].window = -1; } rc = win_init(gfx_init[resolution], GNW95_reset_mode, a2); if (rc != WINDOW_MANAGER_OK) { switch (rc) { case WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE: sprintf(err, "Error initializing video mode %dx%d\n", xres, yres); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_NO_MEMORY: sprintf(err, "Not enough memory to initialize video mode\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS: sprintf(err, "Couldn't find/load text fonts\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED: sprintf(err, "Attempt to initialize window system twice\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_WINDOW_SYSTEM_NOT_INITIALIZED: sprintf(err, "Window system not initialized\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_CURRENT_WINDOWS_TOO_BIG: sprintf(err, "Current windows are too big for new resolution\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE: sprintf(err, "Error initializing default database.\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_8: exit(1); break; case WINDOW_MANAGER_ERR_ALREADY_RUNNING: sprintf(err, "Program already running.\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_TITLE_NOT_SET: sprintf(err, "Program title not set.\n"); GNWSystemError(err); exit(1); break; case WINDOW_MANAGER_ERR_INITIALIZING_INPUT: sprintf(err, "Failure initializing input devices.\n"); GNWSystemError(err); exit(1); break; default: sprintf(err, "Unknown error code %d\n", rc); GNWSystemError(err); exit(1); break; } } currentFont = 100; text_font(100); initMousemgr(); mousemgrSetNameMangler(interpretMangleName); for (i = 0; i < 64; i++) { for (j = 0; j < 256; j++) { alphaBlendTable[(i << 8) + j] = ((i * j) >> 9); } } } // NOTE: Unused. // // 0x4B9454 void windowSetWindowFuncs(ManagedWindowCreateCallback* createCallback, ManagedWindowSelectFunc* selectCallback, WindowDeleteCallback* deleteCallback, DisplayInWindowCallback* displayCallback) { if (createCallback != NULL) { createWindowFunc = createCallback; } if (selectCallback != NULL) { selectWindowFunc = selectCallback; } if (deleteCallback != NULL) { deleteWindowFunc = deleteCallback; } if (displayCallback != NULL) { displayFunc = displayCallback; } } // 0x4B947C void windowClose() { for (int index = 0; index < MANAGED_WINDOW_COUNT; index++) { ManagedWindow* managedWindow = &(windows[index]); if (managedWindow->window != -1) { deleteWindow(managedWindow->name); } } if (inputFunc != NULL) { myfree(inputFunc, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1573 } mousemgrClose(); db_exit(); win_exit(); } // Deletes button with the specified name or all buttons if it's NULL. // // 0x4B9548 bool windowDeleteButton(const char* buttonName) { if (currentWindow != -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttonsLength == 0) { return false; } if (buttonName == NULL) { for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); win_delete_button(managedButton->btn); if (managedButton->hover != NULL) { myfree(managedButton->hover, __FILE__, __LINE__); // "..\int\WINDOW.C", 1648 managedButton->hover = NULL; } if (managedButton->field_4C != NULL) { myfree(managedButton->field_4C, __FILE__, __LINE__); // "..\int\WINDOW.C", 1649 managedButton->field_4C = NULL; } if (managedButton->pressed != NULL) { myfree(managedButton->pressed, __FILE__, __LINE__); // "..\int\WINDOW.C", 1650 managedButton->pressed = NULL; } if (managedButton->normal != NULL) { myfree(managedButton->normal, __FILE__, __LINE__); // "..\int\WINDOW.C", 1651 managedButton->normal = NULL; } if (managedButton->field_50 != NULL) { myfree(managedButton->normal, __FILE__, __LINE__); // "..\int\WINDOW.C", 1652 managedButton->field_50 = NULL; } } myfree(managedWindow->buttons, __FILE__, __LINE__); // "..\int\WINDOW.C", 1654 managedWindow->buttons = NULL; managedWindow->buttonsLength = 0; return true; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { win_delete_button(managedButton->btn); if (managedButton->hover != NULL) { myfree(managedButton->hover, __FILE__, __LINE__); // "..\int\WINDOW.C", 1665 managedButton->hover = NULL; } if (managedButton->field_4C != NULL) { myfree(managedButton->field_4C, __FILE__, __LINE__); // "..\int\WINDOW.C", 1666 managedButton->field_4C = NULL; } if (managedButton->pressed != NULL) { myfree(managedButton->pressed, __FILE__, __LINE__); // "..\int\WINDOW.C", 1667 managedButton->pressed = NULL; } if (managedButton->normal != NULL) { myfree(managedButton->normal, __FILE__, __LINE__); // "..\int\WINDOW.C", 1668 managedButton->normal = NULL; } // FIXME: Probably leaking field_50. It's freed when deleting all // buttons, but not the specific button. if (index != managedWindow->buttonsLength - 1) { // Move remaining buttons up. The last item is not reclaimed. memcpy(managedWindow->buttons + index, managedWindow->buttons + index + 1, sizeof(*(managedWindow->buttons)) * (managedWindow->buttonsLength - index - 1)); } managedWindow->buttonsLength--; if (managedWindow->buttonsLength == 0) { myfree(managedWindow->buttons, __FILE__, __LINE__); // "..\int\WINDOW.C", 1672 managedWindow->buttons = NULL; } return true; } } return false; } // NOTE: Unused. // // 0x4B97F8 void windowEnableButton(const char* buttonName, int enabled) { int index; for (index = 0; index < windows[currentWindow].buttonsLength; index++) { if (stricmp(windows[currentWindow].buttons[index].name, buttonName) == 0) { if (enabled) { if (soundPressFunc != NULL || soundReleaseFunc != NULL) { win_register_button_sound_func(windows[currentWindow].buttons[index].btn, soundPressFunc, soundReleaseFunc); } windows[currentWindow].buttons[index].flags &= ~0x02; } else { if (soundDisableFunc != NULL) { win_register_button_sound_func(windows[currentWindow].buttons[index].btn, soundDisableFunc, NULL); } windows[currentWindow].buttons[index].flags |= 0x02; } } } } // NOTE: Unused. // // 0x4B98C4 int windowGetButtonID(const char* buttonName) { int index; for (index = 0; index < windows[currentWindow].buttonsLength; index++) { if (stricmp(windows[currentWindow].buttons[index].name, buttonName) == 0) { return windows[currentWindow].buttons[index].btn; } } return -1; } // 0x4B9928 bool windowSetButtonFlag(const char* buttonName, int value) { if (currentWindow != -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttons == NULL) { return false; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { managedButton->flags |= value; return true; } } return false; } // NOTE: Unused. // // 0x4B99B4 void windowRegisterButtonSoundFunc(ButtonCallback* soundPressFunc, ButtonCallback* soundReleaseFunc, ButtonCallback* soundDisableFunc) { soundPressFunc = soundPressFunc; soundReleaseFunc = soundReleaseFunc; soundDisableFunc = soundDisableFunc; } // 0x4B99C8 bool windowAddButton(const char* buttonName, int x, int y, int width, int height, int flags) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); int index; for (index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { win_delete_button(managedButton->btn); if (managedButton->hover != NULL) { myfree(managedButton->hover, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1748 managedButton->hover = NULL; } if (managedButton->field_4C != NULL) { myfree(managedButton->field_4C, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1749 managedButton->field_4C = NULL; } if (managedButton->pressed != NULL) { myfree(managedButton->pressed, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1750 managedButton->pressed = NULL; } if (managedButton->normal != NULL) { myfree(managedButton->normal, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1751 managedButton->normal = NULL; } break; } } if (index == managedWindow->buttonsLength) { if (managedWindow->buttons == NULL) { managedWindow->buttons = (ManagedButton*)mymalloc(sizeof(*managedWindow->buttons), __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1758 } else { managedWindow->buttons = (ManagedButton*)myrealloc(managedWindow->buttons, sizeof(*managedWindow->buttons) * (managedWindow->buttonsLength + 1), __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1761 } managedWindow->buttonsLength += 1; } x = (int)(x * managedWindow->field_54); y = (int)(y * managedWindow->field_58); width = (int)(width * managedWindow->field_54); height = (int)(height * managedWindow->field_58); ManagedButton* managedButton = &(managedWindow->buttons[index]); strncpy(managedButton->name, buttonName, 31); managedButton->program = NULL; managedButton->flags = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP] = 0; managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP] = 0; managedButton->mouseEventCallback = NULL; managedButton->rightMouseEventCallback = NULL; managedButton->field_50 = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN] = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_EXIT] = 0; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_ENTER] = 0; managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN] = 0; managedButton->width = width; managedButton->height = height; managedButton->x = x; managedButton->y = y; unsigned char* normal = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1792 unsigned char* pressed = (unsigned char*)mymalloc(width * height, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 1793 if ((flags & BUTTON_FLAG_TRANSPARENT) != 0) { memset(normal, 0, width * height); memset(pressed, 0, width * height); } else { setButtonGFX(width, height, normal, pressed, NULL); } managedButton->btn = win_register_button( managedWindow->window, x, y, width, height, -1, -1, -1, -1, normal, pressed, NULL, flags); if (soundPressFunc != NULL || soundReleaseFunc != NULL) { win_register_button_sound_func(managedButton->btn, soundPressFunc, soundReleaseFunc); } managedButton->hover = NULL; managedButton->pressed = pressed; managedButton->normal = normal; managedButton->field_18 = flags; managedButton->field_4C = NULL; win_register_button_func(managedButton->btn, doButtonOn, doButtonOff, doButtonPress, doButtonRelease); windowSetButtonFlag(buttonName, 1); if ((flags & BUTTON_FLAG_TRANSPARENT) != 0) { win_register_button_mask(managedButton->btn, normal); } return true; } // 0x4B9DD0 bool windowAddButtonGfx(const char* buttonName, char* pressedFileName, char* normalFileName, char* hoverFileName) { ManagedWindow* managedWindow = &(windows[currentWindow]); for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { int width; int height; if (pressedFileName != NULL) { unsigned char* pressed = loadDataFile(pressedFileName, &width, &height); if (pressed != NULL) { drawScaledBuf(managedButton->pressed, managedButton->width, managedButton->height, pressed, width, height); myfree(pressed, __FILE__, __LINE__); // "..\\int\\WINDOW.C, 1834 } } if (normalFileName != NULL) { unsigned char* normal = loadDataFile(normalFileName, &width, &height); if (normal != NULL) { drawScaledBuf(managedButton->normal, managedButton->width, managedButton->height, normal, width, height); myfree(normal, __FILE__, __LINE__); // "..\\int\\WINDOW.C, 1842 } } if (hoverFileName != NULL) { unsigned char* hover = loadDataFile(normalFileName, &width, &height); if (hover != NULL) { if (managedButton->hover == NULL) { managedButton->hover = (unsigned char*)mymalloc(managedButton->height * managedButton->width, __FILE__, __LINE__); // "..\\int\\WINDOW.C, 1849 } drawScaledBuf(managedButton->hover, managedButton->width, managedButton->height, hover, width, height); myfree(hover, __FILE__, __LINE__); // "..\\int\\WINDOW.C, 1853 } } if ((managedButton->field_18 & 0x20) != 0) { win_register_button_mask(managedButton->btn, managedButton->normal); } win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0); return true; } } return false; } // NOTE: Unused. // // 0x4B9F40 int windowAddButtonMask(const char* buttonName, unsigned char* buffer) { int index; ManagedButton* button; unsigned char* copy; for (index = 0; index < windows[currentWindow].buttonsLength; index++) { button = &(windows[currentWindow].buttons[index]); if (stricmp(button->name, buttonName) == 0) { copy = (unsigned char*)mymalloc(button->width * button->height, __FILE__, __LINE__); // "..\\int\\WINDOW.C, 1871 memcpy(copy, buffer, button->width * button->height); win_register_button_mask(button->btn, copy); button->field_50 = copy; return 1; } } return 0; } // NOTE: Unused. // // 0x4B9FC8 int windowAddButtonBuf(const char* buttonName, unsigned char* normal, unsigned char* pressed, unsigned char* hover, int width, int height, int pitch) { int index; ManagedButton* button; for (index = 0; index < windows[currentWindow].buttonsLength; index++) { button = &(windows[currentWindow].buttons[index]); if (stricmp(button->name, buttonName) == 0) { if (normal != NULL) { memset(button->normal, 0, button->width * button->height); drawScaled(button->normal, button->width, button->height, button->width, normal, width, height, pitch); } if (pressed != NULL) { memset(button->pressed, 0, button->width * button->height); drawScaled(button->pressed, button->width, button->height, button->width, pressed, width, height, pitch); } if (hover != NULL) { memset(button->hover, 0, button->width * button->height); drawScaled(button->hover, button->width, button->height, button->width, hover, width, height, pitch); } if ((button->field_18 & 0x20) != 0) { win_register_button_mask(button->btn, button->normal); } win_register_button_image(button->btn, button->normal, button->pressed, button->hover, 0); return 1; } } return 0; } // 0x4BA11C bool windowAddButtonProc(const char* buttonName, Program* program, int mouseEnterProc, int mouseExitProc, int mouseDownProc, int mouseUpProc) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttons == NULL) { return false; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_ENTER] = mouseEnterProc; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_EXIT] = mouseExitProc; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_DOWN] = mouseDownProc; managedButton->procs[MANAGED_BUTTON_MOUSE_EVENT_BUTTON_UP] = mouseUpProc; managedButton->program = program; return true; } } return false; } // 0x4BA1B4 bool windowAddButtonRightProc(const char* buttonName, Program* program, int rightMouseDownProc, int rightMouseUpProc) { if (currentWindow != -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttons == NULL) { return false; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_UP] = rightMouseUpProc; managedButton->rightProcs[MANAGED_BUTTON_RIGHT_MOUSE_EVENT_BUTTON_DOWN] = rightMouseDownProc; managedButton->program = program; return true; } } return false; } // NOTE: Unused. // // 0x4BA238 bool windowAddButtonCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData) { if (currentWindow != -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttons == NULL) { return false; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { managedButton->mouseEventCallbackUserData = userData; managedButton->mouseEventCallback = callback; return true; } } return false; } // NOTE: Unused. // // 0x4BA2B4 bool windowAddButtonRightCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData) { if (currentWindow != -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttons == NULL) { return false; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { managedButton->rightMouseEventCallback = callback; managedButton->rightMouseEventCallbackUserData = userData; win_register_right_button(managedButton->btn, -1, -1, doRightButtonPress, doRightButtonRelease); return true; } } return false; } // 0x4BA34C bool windowAddButtonText(const char* buttonName, const char* text) { return windowAddButtonTextWithOffsets(buttonName, text, 2, 2, 0, 0); } // 0x4BA364 bool windowAddButtonTextWithOffsets(const char* buttonName, const char* text, int pressedImageOffsetX, int pressedImageOffsetY, int normalImageOffsetX, int normalImageOffsetY) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->buttons == NULL) { return false; } for (int index = 0; index < managedWindow->buttonsLength; index++) { ManagedButton* managedButton = &(managedWindow->buttons[index]); if (stricmp(managedButton->name, buttonName) == 0) { int normalImageHeight = text_height() + 1; int normalImageWidth = text_width(text) + 1; unsigned char* buffer = (unsigned char*)mymalloc(normalImageHeight * normalImageWidth, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 2010 int normalImageX = (managedButton->width - normalImageWidth) / 2 + normalImageOffsetX; int normalImageY = (managedButton->height - normalImageHeight) / 2 + normalImageOffsetY; if (normalImageX < 0) { normalImageWidth -= normalImageX; normalImageX = 0; } if (normalImageX + normalImageWidth >= managedButton->width) { normalImageWidth = managedButton->width - normalImageX; } if (normalImageY < 0) { normalImageHeight -= normalImageY; normalImageY = 0; } if (normalImageY + normalImageHeight >= managedButton->height) { normalImageHeight = managedButton->height - normalImageY; } if (managedButton->normal != NULL) { buf_to_buf(managedButton->normal + managedButton->width * normalImageY + normalImageX, normalImageWidth, normalImageHeight, managedButton->width, buffer, normalImageWidth); } else { memset(buffer, 0, normalImageHeight * normalImageWidth); } text_to_buf(buffer, text, normalImageWidth, normalImageWidth, windowGetTextColor() + windowGetTextFlags()); trans_buf_to_buf(buffer, normalImageWidth, normalImageHeight, normalImageWidth, managedButton->normal + managedButton->width * normalImageY + normalImageX, managedButton->width); int pressedImageWidth = text_width(text) + 1; int pressedImageHeight = text_height() + 1; int pressedImageX = (managedButton->width - pressedImageWidth) / 2 + pressedImageOffsetX; int pressedImageY = (managedButton->height - pressedImageHeight) / 2 + pressedImageOffsetY; if (pressedImageX < 0) { pressedImageWidth -= pressedImageX; pressedImageX = 0; } if (pressedImageX + pressedImageWidth >= managedButton->width) { pressedImageWidth = managedButton->width - pressedImageX; } if (pressedImageY < 0) { pressedImageHeight -= pressedImageY; pressedImageY = 0; } if (pressedImageY + pressedImageHeight >= managedButton->height) { pressedImageHeight = managedButton->height - pressedImageY; } if (managedButton->pressed != NULL) { buf_to_buf(managedButton->pressed + managedButton->width * pressedImageY + pressedImageX, pressedImageWidth, pressedImageHeight, managedButton->width, buffer, pressedImageWidth); } else { memset(buffer, 0, pressedImageHeight * pressedImageWidth); } text_to_buf(buffer, text, pressedImageWidth, pressedImageWidth, windowGetTextColor() + windowGetTextFlags()); trans_buf_to_buf(buffer, pressedImageWidth, normalImageHeight, normalImageWidth, managedButton->pressed + managedButton->width * pressedImageY + pressedImageX, managedButton->width); myfree(buffer, __FILE__, __LINE__); // "..\\int\\WINDOW.C", 2078 if ((managedButton->field_18 & 0x20) != 0) { win_register_button_mask(managedButton->btn, managedButton->normal); } win_register_button_image(managedButton->btn, managedButton->normal, managedButton->pressed, managedButton->hover, 0); return true; } } return false; } // 0x4BA694 bool windowFill(float r, float g, float b) { int colorIndex; int wid; colorIndex = ((int)(r * 31.0) << 10) | ((int)(g * 31.0) << 5) | (int)(b * 31.0); // NOTE: Uninline. wid = windowGetGNWID(); win_fill(wid, 0, 0, windowWidth(), windowHeight(), colorTable[colorIndex]); return true; } // 0x4BA738 bool windowFillRect(int x, int y, int width, int height, float r, float g, float b) { ManagedWindow* managedWindow; int colorIndex; int wid; managedWindow = &(windows[currentWindow]); x = (int)(x * managedWindow->field_54); y = (int)(y * managedWindow->field_58); width = (int)(width * managedWindow->field_54); height = (int)(height * managedWindow->field_58); colorIndex = ((int)(r * 31.0) << 10) | ((int)(g * 31.0) << 5) | (int)(b * 31.0); // NOTE: Uninline. wid = windowGetGNWID(); win_fill(wid, x, y, width, height, colorTable[colorIndex]); return true; } // TODO: There is a value returned, not sure which one - could be either // currentRegionIndex or points array. For now it can be safely ignored since // the only caller of this function is op_addregion, which ignores the returned // value. // // 0x4BA844 void windowEndRegion() { ManagedWindow* managedWindow = &(windows[currentWindow]); Region* region = managedWindow->regions[managedWindow->currentRegionIndex]; windowAddRegionPoint(region->points->x, region->points->y, false); regionSetBound(region); } // NOTE: Unused. // // 0x4BA8A4 void* windowRegionGetUserData(const char* windowRegionName) { int index; char* regionName; if (currentWindow == -1) { return NULL; } for (index = 0; index < windows[currentWindow].regionsLength; index++) { regionName = windows[currentWindow].regions[index]->name; if (stricmp(regionName, windowRegionName) == 0) { return regionGetUserData(windows[currentWindow].regions[index]); } } return NULL; } // NOTE: Unused. // // 0x4BA914 void windowRegionSetUserData(const char* windowRegionName, void* userData) { int index; char* regionName; if (currentWindow == -1) { return; } for (index = 0; index < windows[currentWindow].regionsLength; index++) { regionName = windows[currentWindow].regions[index]->name; if (stricmp(regionName, windowRegionName) == 0) { regionSetUserData(windows[currentWindow].regions[index], userData); return; } } } // 0x4BA988 bool windowCheckRegionExists(const char* regionName) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->window == -1) { return false; } for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (stricmp(regionGetName(region), regionName) == 0) { return true; } } } return false; } // 0x4BA9FC bool windowStartRegion(int initialCapacity) { if (currentWindow == -1) { return false; } int newRegionIndex; ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->regions == NULL) { managedWindow->regions = (Region**)mymalloc(sizeof(&(managedWindow->regions)), __FILE__, __LINE__); // "..\int\WINDOW.C", 2167 managedWindow->regionsLength = 1; newRegionIndex = 0; } else { newRegionIndex = 0; for (int index = 0; index < managedWindow->regionsLength; index++) { if (managedWindow->regions[index] == NULL) { break; } newRegionIndex++; } if (newRegionIndex == managedWindow->regionsLength) { managedWindow->regions = (Region**)myrealloc(managedWindow->regions, sizeof(&(managedWindow->regions)) * (managedWindow->regionsLength + 1), __FILE__, __LINE__); // "..\int\WINDOW.C", 2178 managedWindow->regionsLength++; } } Region* newRegion; if (initialCapacity != 0) { newRegion = allocateRegion(initialCapacity + 1); } else { newRegion = NULL; } managedWindow->regions[newRegionIndex] = newRegion; managedWindow->currentRegionIndex = newRegionIndex; return true; } // 0x4BAB68 bool windowAddRegionPoint(int x, int y, bool a3) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); Region* region = managedWindow->regions[managedWindow->currentRegionIndex]; if (region == NULL) { region = managedWindow->regions[managedWindow->currentRegionIndex] = allocateRegion(1); } if (a3) { x = (int)(x * managedWindow->field_54); y = (int)(y * managedWindow->field_58); } regionAddPoint(region, x, y); return true; } // NOTE: Unused. // // 0x4BAC5 int windowAddRegionRect(int a1, int a2, int a3, int a4, int a5) { windowAddRegionPoint(a1, a2, a5); windowAddRegionPoint(a3, a2, a5); windowAddRegionPoint(a3, a4, a5); windowAddRegionPoint(a1, a4, a5); return 0; } // NOTE: Unused. // // 0x4BACA0 int windowAddRegionCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData) { int index; Region* region; if (currentWindow == -1) { return 0; } for (index = 0; index < windows[currentWindow].regionsLength; index++) { region = windows[currentWindow].regions[index]; if (region != NULL && stricmp(region->name, regionName) == 0) { region->mouseEventCallback = callback; region->mouseEventCallbackUserData = userData; return 1; } } return 0; } // NOTE: Unused. // // 0x4BAD30 int windowAddRegionRightCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData) { int index; Region* region; if (currentWindow == -1) { return 0; } for (index = 0; index < windows[currentWindow].regionsLength; index++) { region = windows[currentWindow].regions[index]; if (region != NULL && stricmp(region->name, regionName) == 0) { region->rightMouseEventCallback = callback; region->rightMouseEventCallbackUserData = userData; return 1; } } return 0; } // 0x4BADC0 bool windowAddRegionProc(const char* regionName, Program* program, int a3, int a4, int a5, int a6) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (stricmp(region->name, regionName) == 0) { region->procs[2] = a3; region->procs[3] = a4; region->procs[0] = a5; region->procs[1] = a6; region->program = program; return true; } } } return false; } // 0x4BAE8C bool windowAddRegionRightProc(const char* regionName, Program* program, int a3, int a4) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (stricmp(region->name, regionName) == 0) { region->rightProcs[0] = a3; region->rightProcs[1] = a4; region->program = program; return true; } } } return false; } // 0x4BAF2C bool windowSetRegionFlag(const char* regionName, int value) { if (currentWindow != -1) { ManagedWindow* managedWindow = &(windows[currentWindow]); for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (stricmp(region->name, regionName) == 0) { regionSetFlag(region, value); return true; } } } } return false; } // 0x4BAFA8 bool windowAddRegionName(const char* regionName) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); Region* region = managedWindow->regions[managedWindow->currentRegionIndex]; if (region == NULL) { return false; } for (int index = 0; index < managedWindow->regionsLength; index++) { if (index != managedWindow->currentRegionIndex) { Region* other = managedWindow->regions[index]; if (other != NULL) { if (stricmp(regionGetName(other), regionName) == 0) { regionDelete(other); managedWindow->regions[index] = NULL; break; } } } } regionAddName(region, regionName); return true; } // Delete region with the specified name or all regions if it's NULL. // // 0x4BB0A8 bool windowDeleteRegion(const char* regionName) { if (currentWindow == -1) { return false; } ManagedWindow* managedWindow = &(windows[currentWindow]); if (managedWindow->window == -1) { return false; } if (regionName != NULL) { for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { if (stricmp(regionGetName(region), regionName) == 0) { regionDelete(region); managedWindow->regions[index] = NULL; managedWindow->field_38++; return true; } } } return false; } managedWindow->field_38++; if (managedWindow->regions != NULL) { for (int index = 0; index < managedWindow->regionsLength; index++) { Region* region = managedWindow->regions[index]; if (region != NULL) { regionDelete(region); } } myfree(managedWindow->regions, __FILE__, __LINE__); // "..\int\WINDOW.C", 2353 managedWindow->regions = NULL; managedWindow->regionsLength = 0; } return true; } // 0x4BB220 void updateWindows() { movieUpdate(); mousemgrUpdate(); checkAllRegions(); update_widgets(); } // 0x4BB234 int windowMoviePlaying() { return moviePlaying(); } // 0x4BB23C bool windowSetMovieFlags(int flags) { if (movieSetFlags(flags) != 0) { return false; } return true; } // 0x4BB24C bool windowPlayMovie(char* filePath) { int wid; // NOTE: Uninline. wid = windowGetGNWID(); if (movieRun(wid, filePath) != 0) { return false; } return true; } // 0x4BB280 bool windowPlayMovieRect(char* filePath, int a2, int a3, int a4, int a5) { int wid; // NOTE: Uninline. wid = windowGetGNWID(); if (movieRunRect(wid, filePath, a2, a3, a4, a5) != 0) { return false; } return true; } // 0x4BB2C4 void windowStopMovie() { movieStop(); } // 0x4BB3A8 void drawScaled(unsigned char* dest, int destWidth, int destHeight, int destPitch, unsigned char* src, int srcWidth, int srcHeight, int srcPitch) { if (destWidth == srcWidth && destHeight == srcHeight) { buf_to_buf(src, srcWidth, srcHeight, srcPitch, dest, destPitch); return; } int incrementX = (srcWidth << 16) / destWidth; int incrementY = (srcHeight << 16) / destHeight; int stepX = incrementX >> 16; int stepY = incrementY >> 16; int destSkip = destPitch - destWidth; int srcSkip = stepY * srcPitch; if (srcSkip != 0) { // Downscaling. int srcPosY = 0; for (int y = 0; y < destHeight; y++) { int srcPosX = 0; int offset = 0; for (int x = 0; x < destWidth; x++) { *dest++ = src[offset]; offset += stepX; srcPosX += incrementX; if (srcPosX >= 0x10000) { srcPosX &= 0xFFFF; } } dest += destSkip; src += srcSkip; srcPosY += stepY; if (srcPosY >= 0x10000) { srcPosY &= 0xFFFF; src += srcPitch; } } } else { // Upscaling. int y = 0; int srcPosY = 0; while (y < destHeight) { unsigned char* destPtr = dest; int srcPosX = 0; int offset = 0; for (int x = 0; x < destWidth; x++) { *dest++ = src[offset]; offset += stepX; srcPosX += stepX; if (srcPosX >= 0x10000) { offset++; srcPosX &= 0xFFFF; } } y++; if (y < destHeight) { dest += destSkip; srcPosY += incrementY; while (y < destHeight && srcPosY < 0x10000) { memcpy(dest, destPtr, destWidth); dest += destWidth; srcPosY += incrementY; y++; } srcPosY &= 0xFFFF; src += srcPitch; } } } } // 0x4BB5D0 void drawScaledBuf(unsigned char* dest, int destWidth, int destHeight, unsigned char* src, int srcWidth, int srcHeight) { if (destWidth == srcWidth && destHeight == srcHeight) { memcpy(dest, src, srcWidth * srcHeight); return; } int incrementX = (srcWidth << 16) / destWidth; int incrementY = (srcHeight << 16) / destHeight; int stepX = incrementX >> 16; int stepY = incrementY >> 16; int srcSkip = stepY * srcWidth; if (srcSkip != 0) { // Downscaling. int srcPosY = 0; for (int y = 0; y < destHeight; y++) { int srcPosX = 0; int offset = 0; for (int x = 0; x < destWidth; x++) { *dest++ = src[offset]; offset += stepX; srcPosX += incrementX; if (srcPosX >= 0x10000) { srcPosX &= 0xFFFF; } } src += srcSkip; srcPosY += stepY; if (srcPosY >= 0x10000) { srcPosY &= 0xFFFF; src += srcWidth; } } } else { // Upscaling. int y = 0; int srcPosY = 0; while (y < destHeight) { unsigned char* destPtr = dest; int srcPosX = 0; int offset = 0; for (int x = 0; x < destWidth; x++) { *dest++ = src[offset]; offset += stepX; srcPosX += stepX; if (srcPosX >= 0x10000) { offset++; srcPosX &= 0xFFFF; } } y++; if (y < destHeight) { srcPosY += incrementY; while (y < destHeight && srcPosY < 0x10000) { memcpy(dest, destPtr, destWidth); dest += destWidth; srcPosY += incrementY; y++; } srcPosY &= 0xFFFF; src += srcWidth; } } } } // 0x4BB7D8 void alphaBltBuf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* alphaWindowBuffer, unsigned char* alphaBuffer, unsigned char* dest, int destPitch) { for (int y = 0; y < srcHeight; y++) { for (int x = 0; x < srcWidth; x++) { int rle = (alphaBuffer[0] << 8) + alphaBuffer[1]; alphaBuffer += 2; if ((rle & 0x8000) != 0) { rle &= ~0x8000; } else if ((rle & 0x4000) != 0) { rle &= ~0x4000; memcpy(dest, src, rle); } else { unsigned char* destPtr = dest; unsigned char* srcPtr = src; unsigned char* alphaWindowBufferPtr = alphaWindowBuffer; unsigned char* alphaBufferPtr = alphaBuffer; for (int index = 0; index < rle; index++) { // TODO: Check. unsigned char* v1 = &(cmap[*srcPtr * 3]); unsigned char* v2 = &(cmap[*alphaWindowBufferPtr * 3]); unsigned char alpha = *alphaBufferPtr; // NOTE: Original code is slightly different. unsigned int r = alphaBlendTable[(v1[0] << 8) | alpha] + alphaBlendTable[(v2[0] << 8) | alpha]; unsigned int g = alphaBlendTable[(v1[1] << 8) | alpha] + alphaBlendTable[(v2[1] << 8) | alpha]; unsigned int b = alphaBlendTable[(v1[2] << 8) | alpha] + alphaBlendTable[(v2[2] << 8) | alpha]; unsigned int colorIndex = (r << 10) | (g << 5) | b; *destPtr = colorTable[colorIndex]; destPtr++; srcPtr++; alphaWindowBufferPtr++; alphaBufferPtr++; } alphaBuffer += rle; if ((rle & 1) != 0) { alphaBuffer++; } } src += rle; dest += rle; alphaWindowBuffer += rle; } src += srcPitch - srcWidth; dest += destPitch - srcWidth; } } // 0x4BBFC4 void alphaBltBufRect(unsigned char* src, int srcWidth, int srcHeight, unsigned char* dest, int destWidth, int destHeight) { int chunkWidth = srcWidth / 3; int chunkHeight = srcHeight / 3; // Middle Middle unsigned char* ptr = src + srcWidth * chunkHeight + chunkWidth; for (int x = 0; x < destWidth; x += chunkWidth) { for (int y = 0; y < destHeight; y += chunkHeight) { int middleWidth; if (x + chunkWidth >= destWidth) { middleWidth = destWidth - x; } else { middleWidth = chunkWidth; } int middleY = y + chunkHeight; if (middleY >= destHeight) { middleY = destHeight; } buf_to_buf(ptr, middleWidth, middleY - y, srcWidth, dest + destWidth * y + x, destWidth); } } // Middle Column for (int x = 0; x < destWidth; x += chunkWidth) { // Top Middle int topMiddleX = chunkWidth + x; if (topMiddleX >= destWidth) { topMiddleX = destWidth; } int topMiddleHeight = chunkHeight; if (topMiddleHeight >= destHeight) { topMiddleHeight = destHeight; } buf_to_buf(src + chunkWidth, topMiddleX - x, topMiddleHeight, srcWidth, dest + x, destWidth); // Bottom Middle int bottomMiddleX = chunkWidth + x; if (bottomMiddleX >= destWidth) { bottomMiddleX = destWidth; } buf_to_buf(src + srcWidth * 2 * chunkHeight + chunkWidth, bottomMiddleX - x, destHeight - (destHeight - chunkHeight), srcWidth, dest + destWidth * (destHeight - chunkHeight) + x, destWidth); } // Middle Row for (int y = 0; y < destHeight; y += chunkHeight) { // Middle Left int middleLeftWidth = chunkWidth; if (middleLeftWidth >= destWidth) { middleLeftWidth = destWidth; } int middleLeftY = chunkHeight + y; if (middleLeftY >= destHeight) { middleLeftY = destHeight; } buf_to_buf(src + srcWidth * chunkHeight, middleLeftWidth, middleLeftY - y, srcWidth, dest + destWidth * y, destWidth); // Middle Right int middleRightY = chunkHeight + y; if (middleRightY >= destHeight) { middleRightY = destHeight; } buf_to_buf(src + 2 * chunkWidth + srcWidth * chunkHeight, destWidth - (destWidth - chunkWidth), middleRightY - y, srcWidth, dest + destWidth * y + destWidth - chunkWidth, destWidth); } // Top Left int topLeftWidth = chunkWidth; if (topLeftWidth >= destWidth) { topLeftWidth = destWidth; } int topLeftHeight = chunkHeight; if (topLeftHeight >= destHeight) { topLeftHeight = destHeight; } buf_to_buf(src, topLeftWidth, topLeftHeight, srcWidth, dest, destWidth); // Bottom Left int bottomLeftHeight = chunkHeight; if (chunkHeight >= destHeight) { bottomLeftHeight = destHeight; } buf_to_buf(src + chunkWidth * 2, destWidth - (destWidth - chunkWidth), bottomLeftHeight, srcWidth, dest + destWidth - chunkWidth, destWidth); // Top Right int topRightWidth = chunkWidth; if (chunkWidth >= destWidth) { topRightWidth = destWidth; } buf_to_buf(src + srcWidth * 2 * chunkHeight, topRightWidth, destHeight - (destHeight - chunkHeight), srcWidth, dest + destWidth * (destHeight - chunkHeight), destWidth); // Bottom Right buf_to_buf(src + 2 * chunkWidth + srcWidth * 2 * chunkHeight, destWidth - (destWidth - chunkWidth), destHeight - (destHeight - chunkHeight), srcWidth, dest + destWidth * (destHeight - chunkHeight) + (destWidth - chunkWidth), destWidth); } // NOTE: Unused. // // 0x4BC5E0 int windowEnableCheckRegion() { checkRegionEnable = 1; return 1; } // NOTE: Unused. // // 0x4BC5F0 int windowDisableCheckRegion() { checkRegionEnable = 0; return 1; } // NOTE: Unused. // // 0x4BC600 int windowSetHoldTime(int value) { holdTime = value; return 1; } // NOTE: Unused. // // 0x4BC60C int windowAddTextRegion(int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor) { if (currentWindow == -1) { return -1; } if (windows[currentWindow].window == -1) { return -1; } return win_add_text_region(windows[currentWindow].window, x, y, width, font, textAlignment, textFlags, backgroundColor); } // NOTE: Unused. // // 0x4BC668 int windowPrintTextRegion(int textRegionId, char* string) { return win_print_text_region(textRegionId, string); } // NOTE: Unused. // // 0x4BC670 int windowUpdateTextRegion(int textRegionId) { return win_update_text_region(textRegionId); } // NOTE: Unused. // // 0x4BC678 int windowDeleteTextRegion(int textRegionId) { return win_delete_text_region(textRegionId); } // NOTE: Unused. // // 0x4BC680 int windowTextRegionStyle(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor) { return win_text_region_style(textRegionId, font, textAlignment, textFlags, backgroundColor); } // NOTE: Unused. // // 0x4BC698 int windowAddTextInputRegion(int textRegionId, char* text, int a3, int a4) { return win_add_text_input_region(textRegionId, text, a3, a4); } // NOTE: Unused. // // 0x4BC6A0 int windowDeleteTextInputRegion(int textInputRegionId) { if (textInputRegionId != -1) { return win_delete_text_input_region(textInputRegionId); } if (currentWindow == -1) { return 0; } if (windows[currentWindow].window == -1) { return 0; } return win_delete_all_text_input_regions(windows[currentWindow].window); } // NOTE: Unused. // // 0x4BC6E4 int windowSetTextInputDeleteFunc(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData) { return win_set_text_input_delete_func(textInputRegionId, deleteFunc, userData); } ================================================ FILE: src/int/window.h ================================================ #ifndef WINDOW_H #define WINDOW_H #include #include "plib/gnw/rect.h" #include "int/intrpret.h" #include "int/region.h" #include "int/widget.h" #include "plib/gnw/gnw.h" #define MANAGED_WINDOW_COUNT (16) typedef bool(WindowInputHandler)(int key); typedef void(WindowDeleteCallback)(int windowIndex, const char* windowName); typedef void(DisplayInWindowCallback)(int windowIndex, const char* windowName, unsigned char* data, int width, int height); typedef void(ManagedButtonMouseEventCallback)(void* userData, int eventType); typedef void(ManagedWindowCreateCallback)(int windowIndex, const char* windowName, int* flagsPtr); typedef void(ManagedWindowSelectFunc)(int windowIndex, const char* windowName); typedef enum TextAlignment { TEXT_ALIGNMENT_LEFT, TEXT_ALIGNMENT_RIGHT, TEXT_ALIGNMENT_CENTER, } TextAlignment; int windowGetFont(); int windowSetFont(int a1); void windowResetTextAttributes(); int windowGetTextFlags(); int windowSetTextFlags(int a1); unsigned char windowGetTextColor(); unsigned char windowGetHighlightColor(); int windowSetTextColor(float r, float g, float b); int windowSetHighlightColor(float r, float g, float b); bool windowCheckRegion(int windowIndex, int mouseX, int mouseY, int mouseEvent); bool windowRefreshRegions(); void windowAddInputFunc(WindowInputHandler* handler); bool windowActivateRegion(const char* regionName, int a2); int getInput(); int windowHide(); int windowShow(); int windowDraw(); int windowDrawRect(int left, int top, int right, int bottom); int windowDrawRectID(int windowId, int left, int top, int right, int bottom); int windowWidth(); int windowHeight(); int windowSX(); int windowSY(); int pointInWindow(int x, int y); int windowGetRect(Rect* rect); int windowGetID(); int windowGetGNWID(); int windowGetSpecificGNWID(int windowIndex); bool deleteWindow(const char* windowName); int resizeWindow(const char* windowName, int x, int y, int width, int height); int scaleWindow(const char* windowName, int x, int y, int width, int height); int createWindow(const char* windowName, int x, int y, int width, int height, int a6, int flags); int windowOutput(char* string); bool windowGotoXY(int x, int y); bool selectWindowID(int index); int selectWindow(const char* windowName); int windowGetDefined(const char* name); unsigned char* windowGetBuffer(); char* windowGetName(); int pushWindow(const char* windowName); int popWindow(); void windowPrintBuf(int win, char* string, int stringLength, int width, int maxY, int x, int y, int flags, int textAlignment); char** windowWordWrap(char* string, int maxLength, int a3, int* substringListLengthPtr); void windowFreeWordList(char** substringList, int substringListLength); void windowWrapLineWithSpacing(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment, int a9); void windowWrapLine(int win, char* string, int width, int height, int x, int y, int flags, int textAlignment); bool windowPrintRect(char* string, int a2, int textAlignment); bool windowFormatMessage(char* string, int x, int y, int width, int height, int textAlignment); int windowFormatMessageColor(char* string, int x, int y, int width, int height, int textAlignment, int flags); bool windowPrint(char* string, int a2, int x, int y, int a5); int windowPrintFont(char* string, int a2, int x, int y, int a5, int font); void displayInWindow(unsigned char* data, int width, int height, int pitch); void displayFile(char* fileName); void displayFileRaw(char* fileName); int windowDisplayRaw(char* fileName); bool windowDisplay(char* fileName, int x, int y, int width, int height); int windowDisplayScaled(char* fileName, int x, int y, int width, int height); bool windowDisplayBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight); int windowDisplayTransBuf(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight); int windowDisplayBufScaled(unsigned char* src, int srcWidth, int srcHeight, int destX, int destY, int destWidth, int destHeight); int windowGetXres(); int windowGetYres(); void initWindow(int resolution, int a2); void windowSetWindowFuncs(ManagedWindowCreateCallback* createCallback, ManagedWindowSelectFunc* selectCallback, WindowDeleteCallback* deleteCallback, DisplayInWindowCallback* displayCallback); void windowClose(); bool windowDeleteButton(const char* buttonName); void windowEnableButton(const char* buttonName, int enabled); int windowGetButtonID(const char* buttonName); bool windowSetButtonFlag(const char* buttonName, int value); void windowRegisterButtonSoundFunc(ButtonCallback* soundPressFunc, ButtonCallback* soundReleaseFunc, ButtonCallback* soundDisableFunc); bool windowAddButton(const char* buttonName, int x, int y, int width, int height, int flags); bool windowAddButtonGfx(const char* buttonName, char* a2, char* a3, char* a4); int windowAddButtonMask(const char* buttonName, unsigned char* buffer); int windowAddButtonBuf(const char* buttonName, unsigned char* normal, unsigned char* pressed, unsigned char* hover, int width, int height, int pitch); bool windowAddButtonProc(const char* buttonName, Program* program, int mouseEnterProc, int mouseExitProc, int mouseDownProc, int mouseUpProc); bool windowAddButtonRightProc(const char* buttonName, Program* program, int rightMouseDownProc, int rightMouseUpProc); bool windowAddButtonCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData); bool windowAddButtonRightCfunc(const char* buttonName, ManagedButtonMouseEventCallback* callback, void* userData); bool windowAddButtonText(const char* buttonName, const char* text); bool windowAddButtonTextWithOffsets(const char* buttonName, const char* text, int pressedImageOffsetX, int pressedImageOffsetY, int normalImageOffsetX, int normalImageOffsetY); bool windowFill(float r, float g, float b); bool windowFillRect(int x, int y, int width, int height, float r, float g, float b); void windowEndRegion(); void* windowRegionGetUserData(const char* windowRegionName); void windowRegionSetUserData(const char* windowRegionName, void* userData); bool windowCheckRegionExists(const char* regionName); bool windowStartRegion(int initialCapacity); bool windowAddRegionPoint(int x, int y, bool a3); int windowAddRegionRect(int a1, int a2, int a3, int a4, int a5); int windowAddRegionCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData); int windowAddRegionRightCfunc(const char* regionName, RegionMouseEventCallback* callback, void* userData); bool windowAddRegionProc(const char* regionName, Program* program, int a3, int a4, int a5, int a6); bool windowAddRegionRightProc(const char* regionName, Program* program, int a3, int a4); bool windowSetRegionFlag(const char* regionName, int value); bool windowAddRegionName(const char* regionName); bool windowDeleteRegion(const char* regionName); void updateWindows(); int windowMoviePlaying(); bool windowSetMovieFlags(int flags); bool windowPlayMovie(char* filePath); bool windowPlayMovieRect(char* filePath, int a2, int a3, int a4, int a5); void windowStopMovie(); void drawScaled(unsigned char* dest, int destWidth, int destHeight, int destPitch, unsigned char* src, int srcWidth, int srcHeight, int srcPitch); void drawScaledBuf(unsigned char* dest, int destWidth, int destHeight, unsigned char* src, int srcWidth, int srcHeight); void alphaBltBuf(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* a5, unsigned char* a6, unsigned char* dest, int destPitch); void alphaBltBufRect(unsigned char* src, int srcWidth, int srcHeight, unsigned char* dest, int destWidth, int destHeight); int windowEnableCheckRegion(); int windowDisableCheckRegion(); int windowSetHoldTime(int value); int windowAddTextRegion(int x, int y, int width, int font, int textAlignment, int textFlags, int backgroundColor); int windowPrintTextRegion(int textRegionId, char* string); int windowUpdateTextRegion(int textRegionId); int windowDeleteTextRegion(int textRegionId); int windowTextRegionStyle(int textRegionId, int font, int textAlignment, int textFlags, int backgroundColor); int windowAddTextInputRegion(int textRegionId, char* text, int a3, int a4); int windowDeleteTextInputRegion(int textInputRegionId); int windowSetTextInputDeleteFunc(int textInputRegionId, TextInputRegionDeleteFunc* deleteFunc, void* userData); #endif /* WINDOW_H */ ================================================ FILE: src/memory_defs.h ================================================ #ifndef MEMORY_DEFS_H #define MEMORY_DEFS_H #include typedef void*(MallocProc)(size_t size); typedef void*(ReallocProc)(void* ptr, size_t newSize); typedef void(FreeProc)(void* ptr); #endif /* MEMORY_DEFS_H */ ================================================ FILE: src/mmx.c ================================================ #include "mmx.h" #include #include "plib/gnw/svga.h" // Return `true` if CPU supports MMX. // // 0x4E08A0 bool mmxIsSupported() { int v1; // TODO: There are other ways to determine MMX using FLAGS register. __asm { mov eax, 1 cpuid and edx, 0x800000 mov v1, edx } return v1 != 0; } // 0x4E0DB0 void mmxBlit(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height) { if (mmxEnabled) { // TODO: Blit with MMX. mmxEnabled = false; mmxBlit(dest, destPitch, src, srcPitch, width, height); mmxEnabled = true; } else { for (int y = 0; y < height; y++) { memcpy(dest, src, width); dest += destPitch; src += srcPitch; } } } // 0x4E0ED5 void mmxBlitTrans(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height) { if (mmxEnabled) { // TODO: Blit with MMX. mmxEnabled = false; mmxBlitTrans(dest, destPitch, src, srcPitch, width, height); mmxEnabled = true; } else { int destSkip = destPitch - width; int srcSkip = srcPitch - width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned char c = *src++; if (c != 0) { *dest = c; } dest++; } src += srcSkip; dest += destSkip; } } } ================================================ FILE: src/mmx.h ================================================ #ifndef MMX_H #define MMX_H #include bool mmxIsSupported(); void mmxBlit(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height); void mmxBlitTrans(unsigned char* dest, int destPitch, unsigned char* src, int srcPitch, int width, int height); #endif /* MMX_H */ ================================================ FILE: src/movie_lib.c ================================================ // NOTE: This module is completely standalone. It does not have external // dependencies and uses __cdecl calling convention, which probably means it // was implemented as a separate library and linked statically. #include "movie_lib.h" #include #include #include #include // 0x51EBD8 int dword_51EBD8 = 0; // 0x51EBDC int dword_51EBDC = 4; // 0x51EBE0 unsigned short word_51EBE0[256] = { // clang-format off 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002F, 0x0033, 0x0038, 0x003D, 0x0042, 0x0048, 0x004F, 0x0056, 0x005E, 0x0066, 0x0070, 0x007A, 0x0085, 0x0091, 0x009E, 0x00AD, 0x00BD, 0x00CE, 0x00E1, 0x00F5, 0x010B, 0x0124, 0x013E, 0x015C, 0x017B, 0x019E, 0x01C4, 0x01ED, 0x021A, 0x024B, 0x0280, 0x02BB, 0x02FB, 0x0340, 0x038C, 0x03DF, 0x0439, 0x049C, 0x0508, 0x057D, 0x05FE, 0x0689, 0x0722, 0x07C9, 0x087F, 0x0945, 0x0A1E, 0x0B0A, 0x0C0C, 0x0D25, 0x0E58, 0x0FA8, 0x1115, 0x12A4, 0x1458, 0x1633, 0x183A, 0x1A6F, 0x1CD9, 0x1F7B, 0x225A, 0x257D, 0x28E8, 0x2CA4, 0x30B7, 0x3529, 0x3A03, 0x3F4E, 0x4515, 0x4B62, 0x5244, 0x59C5, 0x61F6, 0x6AE7, 0x74A8, 0x7F4D, 0x8AEB, 0x9798, 0xA56E, 0xB486, 0xC4FF, 0xD6F9, 0xEA97, 0xFFFF, 0x0001, 0x0001, 0x1569, 0x2907, 0x3B01, 0x4B7A, 0x5A92, 0x6868, 0x7515, 0x80B3, 0x8B58, 0x9519, 0x9E0A, 0xA63B, 0xADBC, 0xB49E, 0xBAEB, 0xC0B2, 0xC5FD, 0xCAD7, 0xCF49, 0xD35C, 0xD718, 0xDA83, 0xDDA6, 0xE085, 0xE327, 0xE591, 0xE7C6, 0xE9CD, 0xEBA8, 0xED5C, 0xEEEB, 0xF058, 0xF1A8, 0xF2DB, 0xF3F4, 0xF4F6, 0xF5E2, 0xF6BB, 0xF781, 0xF837, 0xF8DE, 0xF977, 0xFA02, 0xFA83, 0xFAF8, 0xFB64, 0xFBC7, 0xFC21, 0xFC74, 0xFCC0, 0xFD05, 0xFD45, 0xFD80, 0xFDB5, 0xFDE6, 0xFE13, 0xFE3C, 0xFE62, 0xFE85, 0xFEA4, 0xFEC2, 0xFEDC, 0xFEF5, 0xFF0B, 0xFF1F, 0xFF32, 0xFF43, 0xFF53, 0xFF62, 0xFF6F, 0xFF7B, 0xFF86, 0xFF90, 0xFF9A, 0xFFA2, 0xFFAA, 0xFFB1, 0xFFB8, 0xFFBE, 0xFFC3, 0xFFC8, 0xFFCD, 0xFFD1, 0xFFD5, 0xFFD6, 0xFFD7, 0xFFD8, 0xFFD9, 0xFFDA, 0xFFDB, 0xFFDC, 0xFFDD, 0xFFDE, 0xFFDF, 0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7, 0xFFE8, 0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFEE, 0xFFED, 0xFFEF, 0xFFF0, 0xFFF1, 0xFFF2, 0xFFF3, 0xFFF4, 0xFFF5, 0xFFF6, 0xFFF7, 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF, // clang-format on }; // 0x51EDE0 LPDIRECTDRAW gMovieLibDirectDraw = NULL; // 0x51EDE4 int _sync_active = 0; // 0x51EDE8 int _sync_late = 0; // 0x51EDEC int _sync_FrameDropped = 0; // 0x51EDF0 LPDIRECTSOUND gMovieLibDirectSound = NULL; // 0x51EDF4 LPDIRECTSOUNDBUFFER gMovieLibDirectSoundBuffer = NULL; // 0x51EDF8 int gMovieLibVolume = 0; // 0x51EDFC int gMovieLibPan = 0; // 0x51EE00 LPDIRECTDRAWSURFACE gMovieDirectDrawSurface1 = NULL; // 0x51EE04 LPDIRECTDRAWSURFACE gMovieDirectDrawSurface2 = NULL; // 0x51EE08 void (*_sf_ShowFrame)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int) = _do_nothing_2; // 0x51EE0C int dword_51EE0C = 1; // TODO: There is a default function (not yet implemented). // // 0x51EE14 void (*_pal_SetPalette)(unsigned char*, int, int) = NULL; // 0x51EE18 int _rm_hold = 0; // 0x51EE1C int _rm_active = 0; // 0x51EE20 bool dword_51EE20 = false; // 0x51F018 int dword_51F018[256]; // 0x51F418 unsigned short word_51F418[256] = { // clang-format off 0xF8F8, 0xF8F9, 0xF8FA, 0xF8FB, 0xF8FC, 0xF8FD, 0xF8FE, 0xF8FF, 0xF800, 0xF801, 0xF802, 0xF803, 0xF804, 0xF805, 0xF806, 0xF807, 0xF9F8, 0xF9F9, 0xF9FA, 0xF9FB, 0xF9FC, 0xF9FD, 0xF9FE, 0xF9FF, 0xF900, 0xF901, 0xF902, 0xF903, 0xF904, 0xF905, 0xF906, 0xF907, 0xFAF8, 0xFAF9, 0xFAFA, 0xFAFB, 0xFAFC, 0xFAFD, 0xFAFE, 0xFAFF, 0xFA00, 0xFA01, 0xFA02, 0xFA03, 0xFA04, 0xFA05, 0xFA06, 0xFA07, 0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF, 0xFB00, 0xFB01, 0xFB02, 0xFB03, 0xFB04, 0xFB05, 0xFB06, 0xFB07, 0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB, 0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF, 0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07, 0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFDFD, 0xFDFE, 0xFDFF, 0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04, 0xFD05, 0xFD06, 0xFD07, 0xFEF8, 0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0xFEFD, 0xFEFE, 0xFEFF, 0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07, 0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF, 0xFF00, 0xFF01, 0xFF02, 0xFF03, 0xFF04, 0xFF05, 0xFF06, 0xFF07, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x01F8, 0x01F9, 0x01FA, 0x01FB, 0x01FC, 0x01FD, 0x01FE, 0x01FF, 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF, 0x0200, 0x0201, 0x0202, 0x0203, 0x0204, 0x0205, 0x0206, 0x0207, 0x03F8, 0x03F9, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF, 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, 0x04F8, 0x04F9, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF, 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x05F8, 0x05F9, 0x05FA, 0x05FB, 0x05FC, 0x05FD, 0x05FE, 0x05FF, 0x0500, 0x0501, 0x0502, 0x0503, 0x0504, 0x0505, 0x0506, 0x0507, 0x06F8, 0x06F9, 0x06FA, 0x06FB, 0x06FC, 0x06FD, 0x06FE, 0x06FF, 0x0600, 0x0601, 0x0602, 0x0603, 0x0604, 0x0605, 0x0606, 0x0607, 0x07F8, 0x07F9, 0x07FA, 0x07FB, 0x07FC, 0x07FD, 0x07FE, 0x07FF, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704, 0x0705, 0x0706, 0x0707, // clang-format on }; // 0x51F618 unsigned short word_51F618[256] = { // clang-format off 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0108, 0x0109, 0x010A, 0x010B, 0x010C, 0x010D, 0x010E, 0x0208, 0x0209, 0x020A, 0x020B, 0x020C, 0x020D, 0x020E, 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x040D, 0x040E, 0x0508, 0x0509, 0x050A, 0x050B, 0x050C, 0x050D, 0x050E, 0x0608, 0x0609, 0x060A, 0x060B, 0x060C, 0x060D, 0x060E, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D, 0x070E, 0x08F2, 0x08F3, 0x08F4, 0x08F5, 0x08F6, 0x08F7, 0x08F8, 0x08F9, 0x08FA, 0x08FB, 0x08FC, 0x08FD, 0x08FE, 0x08FF, 0x0800, 0x0801, 0x0802, 0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809, 0x080A, 0x080B, 0x080C, 0x080D, 0x080E, 0x09F2, 0x09F3, 0x09F4, 0x09F5, 0x09F6, 0x09F7, 0x09F8, 0x09F9, 0x09FA, 0x09FB, 0x09FC, 0x09FD, 0x09FE, 0x09FF, 0x0900, 0x0901, 0x0902, 0x0903, 0x0904, 0x0905, 0x0906, 0x0907, 0x0908, 0x0909, 0x090A, 0x090B, 0x090C, 0x090D, 0x090E, 0x0AF2, 0x0AF3, 0x0AF4, 0x0AF5, 0x0AF6, 0x0AF7, 0x0AF8, 0x0AF9, 0x0AFA, 0x0AFB, 0x0AFC, 0x0AFD, 0x0AFE, 0x0AFF, 0x0A00, 0x0A01, 0x0A02, 0x0A03, 0x0A04, 0x0A05, 0x0A06, 0x0A07, 0x0A08, 0x0A09, 0x0A0A, 0x0A0B, 0x0A0C, 0x0A0D, 0x0A0E, 0x0BF2, 0x0BF3, 0x0BF4, 0x0BF5, 0x0BF6, 0x0BF7, 0x0BF8, 0x0BF9, 0x0BFA, 0x0BFB, 0x0BFC, 0x0BFD, 0x0BFE, 0x0BFF, 0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05, 0x0B06, 0x0B07, 0x0B08, 0x0B09, 0x0B0A, 0x0B0B, 0x0B0C, 0x0B0D, 0x0B0E, 0x0CF2, 0x0CF3, 0x0CF4, 0x0CF5, 0x0CF6, 0x0CF7, 0x0CF8, 0x0CF9, 0x0CFA, 0x0CFB, 0x0CFC, 0x0CFD, 0x0CFE, 0x0CFF, 0x0C00, 0x0C01, 0x0C02, 0x0C03, 0x0C04, 0x0C05, 0x0C06, 0x0C07, 0x0C08, 0x0C09, 0x0C0A, 0x0C0B, 0x0C0C, 0x0C0D, 0x0C0E, 0x0DF2, 0x0DF3, 0x0DF4, 0x0DF5, 0x0DF6, 0x0DF7, 0x0DF8, 0x0DF9, 0x0DFA, 0x0DFB, 0x0DFC, 0x0DFD, 0x0DFE, 0x0DFF, 0x0D00, 0x0D01, 0x0D02, 0x0D03, 0x0D04, 0x0D05, 0x0D06, 0x0D07, 0x0D08, 0x0D09, 0x0D0A, 0x0D0B, 0x0D0C, 0x0D0D, 0x0D0E, 0x0EF2, 0x0EF3, 0x0EF4, 0x0EF5, 0x0EF6, 0x0EF7, 0x0EF8, 0x0EF9, 0x0EFA, 0x0EFB, 0x0EFC, 0x0EFD, 0x0EFE, 0x0EFF, 0x0E00, 0x0E01, 0x0E02, 0x0E03, 0x0E04, 0x0E05, 0x0E06, 0x0E07, 0x0E08, 0x0E09, 0x0E0A, 0x0E0B, // clang-format on }; // 0x51F818 unsigned int _$$R0053[16] = { // clang-format off 0xC3C3C3C3, 0xC3C3C1C3, 0xC3C3C3C1, 0xC3C3C1C1, 0xC1C3C3C3, 0xC1C3C1C3, 0xC1C3C3C1, 0xC1C3C1C1, 0xC3C1C3C3, 0xC3C1C1C3, 0xC3C1C3C1, 0xC3C1C1C1, 0xC1C1C3C3, 0xC1C1C1C3, 0xC1C1C3C1, 0xC1C1C1C1, // clang-format on }; // 0x51F858 unsigned int _$$R0004[256] = { // clang-format off 0xC3C3C3C3, 0xC3C3C2C3, 0xC3C3C1C3, 0xC3C3C5C3, 0xC3C3C3C2, 0xC3C3C2C2, 0xC3C3C1C2, 0xC3C3C5C2, 0xC3C3C3C1, 0xC3C3C2C1, 0xC3C3C1C1, 0xC3C3C5C1, 0xC3C3C3C5, 0xC3C3C2C5, 0xC3C3C1C5, 0xC3C3C5C5, 0xC2C3C3C3, 0xC2C3C2C3, 0xC2C3C1C3, 0xC2C3C5C3, 0xC2C3C3C2, 0xC2C3C2C2, 0xC2C3C1C2, 0xC2C3C5C2, 0xC2C3C3C1, 0xC2C3C2C1, 0xC2C3C1C1, 0xC2C3C5C1, 0xC2C3C3C5, 0xC2C3C2C5, 0xC2C3C1C5, 0xC2C3C5C5, 0xC1C3C3C3, 0xC1C3C2C3, 0xC1C3C1C3, 0xC1C3C5C3, 0xC1C3C3C2, 0xC1C3C2C2, 0xC1C3C1C2, 0xC1C3C5C2, 0xC1C3C3C1, 0xC1C3C2C1, 0xC1C3C1C1, 0xC1C3C5C1, 0xC1C3C3C5, 0xC1C3C2C5, 0xC1C3C1C5, 0xC1C3C5C5, 0xC5C3C3C3, 0xC5C3C2C3, 0xC5C3C1C3, 0xC5C3C5C3, 0xC5C3C3C2, 0xC5C3C2C2, 0xC5C3C1C2, 0xC5C3C5C2, 0xC5C3C3C1, 0xC5C3C2C1, 0xC5C3C1C1, 0xC5C3C5C1, 0xC5C3C3C5, 0xC5C3C2C5, 0xC5C3C1C5, 0xC5C3C5C5, 0xC3C2C3C3, 0xC3C2C2C3, 0xC3C2C1C3, 0xC3C2C5C3, 0xC3C2C3C2, 0xC3C2C2C2, 0xC3C2C1C2, 0xC3C2C5C2, 0xC3C2C3C1, 0xC3C2C2C1, 0xC3C2C1C1, 0xC3C2C5C1, 0xC3C2C3C5, 0xC3C2C2C5, 0xC3C2C1C5, 0xC3C2C5C5, 0xC2C2C3C3, 0xC2C2C2C3, 0xC2C2C1C3, 0xC2C2C5C3, 0xC2C2C3C2, 0xC2C2C2C2, 0xC2C2C1C2, 0xC2C2C5C2, 0xC2C2C3C1, 0xC2C2C2C1, 0xC2C2C1C1, 0xC2C2C5C1, 0xC2C2C3C5, 0xC2C2C2C5, 0xC2C2C1C5, 0xC2C2C5C5, 0xC1C2C3C3, 0xC1C2C2C3, 0xC1C2C1C3, 0xC1C2C5C3, 0xC1C2C3C2, 0xC1C2C2C2, 0xC1C2C1C2, 0xC1C2C5C2, 0xC1C2C3C1, 0xC1C2C2C1, 0xC1C2C1C1, 0xC1C2C5C1, 0xC1C2C3C5, 0xC1C2C2C5, 0xC1C2C1C5, 0xC1C2C5C5, 0xC5C2C3C3, 0xC5C2C2C3, 0xC5C2C1C3, 0xC5C2C5C3, 0xC5C2C3C2, 0xC5C2C2C2, 0xC5C2C1C2, 0xC5C2C5C2, 0xC5C2C3C1, 0xC5C2C2C1, 0xC5C2C1C1, 0xC5C2C5C1, 0xC5C2C3C5, 0xC5C2C2C5, 0xC5C2C1C5, 0xC5C2C5C5, 0xC3C1C3C3, 0xC3C1C2C3, 0xC3C1C1C3, 0xC3C1C5C3, 0xC3C1C3C2, 0xC3C1C2C2, 0xC3C1C1C2, 0xC3C1C5C2, 0xC3C1C3C1, 0xC3C1C2C1, 0xC3C1C1C1, 0xC3C1C5C1, 0xC3C1C3C5, 0xC3C1C2C5, 0xC3C1C1C5, 0xC3C1C5C5, 0xC2C1C3C3, 0xC2C1C2C3, 0xC2C1C1C3, 0xC2C1C5C3, 0xC2C1C3C2, 0xC2C1C2C2, 0xC2C1C1C2, 0xC2C1C5C2, 0xC2C1C3C1, 0xC2C1C2C1, 0xC2C1C1C1, 0xC2C1C5C1, 0xC2C1C3C5, 0xC2C1C2C5, 0xC2C1C1C5, 0xC2C1C5C5, 0xC1C1C3C3, 0xC1C1C2C3, 0xC1C1C1C3, 0xC1C1C5C3, 0xC1C1C3C2, 0xC1C1C2C2, 0xC1C1C1C2, 0xC1C1C5C2, 0xC1C1C3C1, 0xC1C1C2C1, 0xC1C1C1C1, 0xC1C1C5C1, 0xC1C1C3C5, 0xC1C1C2C5, 0xC1C1C1C5, 0xC1C1C5C5, 0xC5C1C3C3, 0xC5C1C2C3, 0xC5C1C1C3, 0xC5C1C5C3, 0xC5C1C3C2, 0xC5C1C2C2, 0xC5C1C1C2, 0xC5C1C5C2, 0xC5C1C3C1, 0xC5C1C2C1, 0xC5C1C1C1, 0xC5C1C5C1, 0xC5C1C3C5, 0xC5C1C2C5, 0xC5C1C1C5, 0xC5C1C5C5, 0xC3C5C3C3, 0xC3C5C2C3, 0xC3C5C1C3, 0xC3C5C5C3, 0xC3C5C3C2, 0xC3C5C2C2, 0xC3C5C1C2, 0xC3C5C5C2, 0xC3C5C3C1, 0xC3C5C2C1, 0xC3C5C1C1, 0xC3C5C5C1, 0xC3C5C3C5, 0xC3C5C2C5, 0xC3C5C1C5, 0xC3C5C5C5, 0xC2C5C3C3, 0xC2C5C2C3, 0xC2C5C1C3, 0xC2C5C5C3, 0xC2C5C3C2, 0xC2C5C2C2, 0xC2C5C1C2, 0xC2C5C5C2, 0xC2C5C3C1, 0xC2C5C2C1, 0xC2C5C1C1, 0xC2C5C5C1, 0xC2C5C3C5, 0xC2C5C2C5, 0xC2C5C1C5, 0xC2C5C5C5, 0xC1C5C3C3, 0xC1C5C2C3, 0xC1C5C1C3, 0xC1C5C5C3, 0xC1C5C3C2, 0xC1C5C2C2, 0xC1C5C1C2, 0xC1C5C5C2, 0xC1C5C3C1, 0xC1C5C2C1, 0xC1C5C1C1, 0xC1C5C5C1, 0xC1C5C3C5, 0xC1C5C2C5, 0xC1C5C1C5, 0xC1C5C5C5, 0xC5C5C3C3, 0xC5C5C2C3, 0xC5C5C1C3, 0xC5C5C5C3, 0xC5C5C3C2, 0xC5C5C2C2, 0xC5C5C1C2, 0xC5C5C5C2, 0xC5C5C3C1, 0xC5C5C2C1, 0xC5C5C1C1, 0xC5C5C5C1, 0xC5C5C3C5, 0xC5C5C2C5, 0xC5C5C1C5, 0xC5C5C5C5, // clang-format on }; // 0x51FC58 unsigned int _$$R0063[256] = { // clang-format off 0xE3C3E3C3, 0xE3C7E3C3, 0xE3C1E3C3, 0xE3C5E3C3, 0xE7C3E3C3, 0xE7C7E3C3, 0xE7C1E3C3, 0xE7C5E3C3, 0xE1C3E3C3, 0xE1C7E3C3, 0xE1C1E3C3, 0xE1C5E3C3, 0xE5C3E3C3, 0xE5C7E3C3, 0xE5C1E3C3, 0xE5C5E3C3, 0xE3C3E3C7, 0xE3C7E3C7, 0xE3C1E3C7, 0xE3C5E3C7, 0xE7C3E3C7, 0xE7C7E3C7, 0xE7C1E3C7, 0xE7C5E3C7, 0xE1C3E3C7, 0xE1C7E3C7, 0xE1C1E3C7, 0xE1C5E3C7, 0xE5C3E3C7, 0xE5C7E3C7, 0xE5C1E3C7, 0xE5C5E3C7, 0xE3C3E3C1, 0xE3C7E3C1, 0xE3C1E3C1, 0xE3C5E3C1, 0xE7C3E3C1, 0xE7C7E3C1, 0xE7C1E3C1, 0xE7C5E3C1, 0xE1C3E3C1, 0xE1C7E3C1, 0xE1C1E3C1, 0xE1C5E3C1, 0xE5C3E3C1, 0xE5C7E3C1, 0xE5C1E3C1, 0xE5C5E3C1, 0xE3C3E3C5, 0xE3C7E3C5, 0xE3C1E3C5, 0xE3C5E3C5, 0xE7C3E3C5, 0xE7C7E3C5, 0xE7C1E3C5, 0xE7C5E3C5, 0xE1C3E3C5, 0xE1C7E3C5, 0xE1C1E3C5, 0xE1C5E3C5, 0xE5C3E3C5, 0xE5C7E3C5, 0xE5C1E3C5, 0xE5C5E3C5, 0xE3C3E7C3, 0xE3C7E7C3, 0xE3C1E7C3, 0xE3C5E7C3, 0xE7C3E7C3, 0xE7C7E7C3, 0xE7C1E7C3, 0xE7C5E7C3, 0xE1C3E7C3, 0xE1C7E7C3, 0xE1C1E7C3, 0xE1C5E7C3, 0xE5C3E7C3, 0xE5C7E7C3, 0xE5C1E7C3, 0xE5C5E7C3, 0xE3C3E7C7, 0xE3C7E7C7, 0xE3C1E7C7, 0xE3C5E7C7, 0xE7C3E7C7, 0xE7C7E7C7, 0xE7C1E7C7, 0xE7C5E7C7, 0xE1C3E7C7, 0xE1C7E7C7, 0xE1C1E7C7, 0xE1C5E7C7, 0xE5C3E7C7, 0xE5C7E7C7, 0xE5C1E7C7, 0xE5C5E7C7, 0xE3C3E7C1, 0xE3C7E7C1, 0xE3C1E7C1, 0xE3C5E7C1, 0xE7C3E7C1, 0xE7C7E7C1, 0xE7C1E7C1, 0xE7C5E7C1, 0xE1C3E7C1, 0xE1C7E7C1, 0xE1C1E7C1, 0xE1C5E7C1, 0xE5C3E7C1, 0xE5C7E7C1, 0xE5C1E7C1, 0xE5C5E7C1, 0xE3C3E7C5, 0xE3C7E7C5, 0xE3C1E7C5, 0xE3C5E7C5, 0xE7C3E7C5, 0xE7C7E7C5, 0xE7C1E7C5, 0xE7C5E7C5, 0xE1C3E7C5, 0xE1C7E7C5, 0xE1C1E7C5, 0xE1C5E7C5, 0xE5C3E7C5, 0xE5C7E7C5, 0xE5C1E7C5, 0xE5C5E7C5, 0xE3C3E1C3, 0xE3C7E1C3, 0xE3C1E1C3, 0xE3C5E1C3, 0xE7C3E1C3, 0xE7C7E1C3, 0xE7C1E1C3, 0xE7C5E1C3, 0xE1C3E1C3, 0xE1C7E1C3, 0xE1C1E1C3, 0xE1C5E1C3, 0xE5C3E1C3, 0xE5C7E1C3, 0xE5C1E1C3, 0xE5C5E1C3, 0xE3C3E1C7, 0xE3C7E1C7, 0xE3C1E1C7, 0xE3C5E1C7, 0xE7C3E1C7, 0xE7C7E1C7, 0xE7C1E1C7, 0xE7C5E1C7, 0xE1C3E1C7, 0xE1C7E1C7, 0xE1C1E1C7, 0xE1C5E1C7, 0xE5C3E1C7, 0xE5C7E1C7, 0xE5C1E1C7, 0xE5C5E1C7, 0xE3C3E1C1, 0xE3C7E1C1, 0xE3C1E1C1, 0xE3C5E1C1, 0xE7C3E1C1, 0xE7C7E1C1, 0xE7C1E1C1, 0xE7C5E1C1, 0xE1C3E1C1, 0xE1C7E1C1, 0xE1C1E1C1, 0xE1C5E1C1, 0xE5C3E1C1, 0xE5C7E1C1, 0xE5C1E1C1, 0xE5C5E1C1, 0xE3C3E1C5, 0xE3C7E1C5, 0xE3C1E1C5, 0xE3C5E1C5, 0xE7C3E1C5, 0xE7C7E1C5, 0xE7C1E1C5, 0xE7C5E1C5, 0xE1C3E1C5, 0xE1C7E1C5, 0xE1C1E1C5, 0xE1C5E1C5, 0xE5C3E1C5, 0xE5C7E1C5, 0xE5C1E1C5, 0xE5C5E1C5, 0xE3C3E5C3, 0xE3C7E5C3, 0xE3C1E5C3, 0xE3C5E5C3, 0xE7C3E5C3, 0xE7C7E5C3, 0xE7C1E5C3, 0xE7C5E5C3, 0xE1C3E5C3, 0xE1C7E5C3, 0xE1C1E5C3, 0xE1C5E5C3, 0xE5C3E5C3, 0xE5C7E5C3, 0xE5C1E5C3, 0xE5C5E5C3, 0xE3C3E5C7, 0xE3C7E5C7, 0xE3C1E5C7, 0xE3C5E5C7, 0xE7C3E5C7, 0xE7C7E5C7, 0xE7C1E5C7, 0xE7C5E5C7, 0xE1C3E5C7, 0xE1C7E5C7, 0xE1C1E5C7, 0xE1C5E5C7, 0xE5C3E5C7, 0xE5C7E5C7, 0xE5C1E5C7, 0xE5C5E5C7, 0xE3C3E5C1, 0xE3C7E5C1, 0xE3C1E5C1, 0xE3C5E5C1, 0xE7C3E5C1, 0xE7C7E5C1, 0xE7C1E5C1, 0xE7C5E5C1, 0xE1C3E5C1, 0xE1C7E5C1, 0xE1C1E5C1, 0xE1C5E5C1, 0xE5C3E5C1, 0xE5C7E5C1, 0xE5C1E5C1, 0xE5C5E5C1, 0xE3C3E5C5, 0xE3C7E5C5, 0xE3C1E5C5, 0xE3C5E5C5, 0xE7C3E5C5, 0xE7C7E5C5, 0xE7C1E5C5, 0xE7C5E5C5, 0xE1C3E5C5, 0xE1C7E5C5, 0xE1C1E5C5, 0xE1C5E5C5, 0xE5C3E5C5, 0xE5C7E5C5, 0xE5C1E5C5, 0xE5C5E5C5, // clang-format on }; // 0x6B3660 int dword_6B3660; // 0x6B3668 DSBCAPS stru_6B3668; // 0x6B367C int _sf_ScreenWidth; // 0x6B3680 int dword_6B3680; // 0x6B3684 int _rm_FrameDropCount; // 0x6B3688 int _snd_buf; // 0x6B3690 STRUCT_6B3690 _io_mem_buf; // 0x6B369C int _io_next_hdr; // 0x6B36A0 int dword_6B36A0; // 0x6B36A4 int dword_6B36A4; // 0x6B36A8 int _rm_FrameCount; // 0x6B36AC int _sf_ScreenHeight; // 0x6B36B0 int dword_6B36B0; // 0x6B36B8 unsigned char _palette_entries1[768]; // 0x6B39B8 MallocProc* gMovieLibMallocProc; // 0x6B39BC int (*_rm_ctl)(); // 0x6B39C0 int _rm_dx; // 0x6B39C4 int _rm_dy; // 0x6B39C8 int _gSoundTimeBase; // 0x6B39CC int _io_handle; // 0x6B39D0 int _rm_len; // 0x6B39D4 FreeProc* gMovieLibFreeProc; // 0x6B39D8 int _snd_comp; // 0x6B39DC unsigned char* _rm_p; // 0x6B39E0 int dword_6B39E0[60]; // 0x6B3AD0 int _sync_wait_quanta; // 0x6B3AD4 int dword_6B3AD4; // 0x6B3AD8 int _rm_track_bit; // 0x6B3ADC int _sync_time; // 0x6B3AE0 MovieReadProc* gMovieLibReadProc; // 0x6B3AE4 int dword_6B3AE4; // 0x6B3AE8 int dword_6B3AE8; // 0x6B3CEC int dword_6B3CEC; // 0x6B3CF0 int dword_6B3CF0; // 0x6B3CF4 int dword_6B3CF4; // 0x6B3CF8 int dword_6B3CF8; // 0x6B3CFC int _mveBW; // 0x6B3D00 int dword_6B3D00; // 0x6B3D04 int dword_6B3D04; // 0x6B3D08 int dword_6B3D08; // 0x6B3D0C unsigned char _pal_tbl[768]; // 0x6B400C unsigned char byte_6B400C; // 0x6B400D unsigned char byte_6B400D; // 0x6B400E int dword_6B400E; // 0x6B4012 int dword_6B4012; // 0x6B4016 unsigned char byte_6B4016; // 0x6B4017 int dword_6B4017; // 0x6B401B int dword_6B401B; // 0x6B401F int dword_6B401F; // 0x6B4023 int dword_6B4023; // 0x6B4027 int dword_6B4027; // 0x6B402B int dword_6B402B; // 0x6B402F int _mveBH; // 0x6B4033 unsigned char* gMovieDirectDrawSurfaceBuffer1; // 0x6B4037 unsigned char* gMovieDirectDrawSurfaceBuffer2; // 0x6B403B int dword_6B403B; // 0x6B403F int dword_6B403F; // 0x4F4800 void movieLibSetMemoryProcs(MallocProc* mallocProc, FreeProc* freeProc) { gMovieLibMallocProc = mallocProc; gMovieLibFreeProc = freeProc; } // 0x4F4860 void movieLibSetReadProc(MovieReadProc* readProc) { gMovieLibReadProc = readProc; } // 0x4F4890 void _MVE_MemInit(STRUCT_6B3690* a1, int a2, void* a3) { if (a3 == NULL) { return; } _MVE_MemFree(a1); a1->field_0 = a3; a1->field_4 = a2; a1->field_8 = 0; } // 0x4F48C0 void _MVE_MemFree(STRUCT_6B3690* a1) { if (a1->field_8 && gMovieLibFreeProc != NULL) { gMovieLibFreeProc(a1->field_0); a1->field_8 = 0; } a1->field_4 = 0; } // 0x4F48F0 void movieLibSetDirectSound(LPDIRECTSOUND ds) { gMovieLibDirectSound = ds; } // 0x4F4900 void movieLibSetVolume(int volume) { gMovieLibVolume = volume; if (gMovieLibDirectSoundBuffer != NULL) { IDirectSoundBuffer_SetVolume(gMovieLibDirectSoundBuffer, volume); } } // 0x4F4920 void movieLibSetPan(int pan) { gMovieLibPan = pan; if (gMovieLibDirectSoundBuffer != NULL) { IDirectSoundBuffer_SetPan(gMovieLibDirectSoundBuffer, pan); } } // 0x4F4940 void _MVE_sfSVGA(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { _sf_ScreenWidth = a1; _sf_ScreenHeight = a2; dword_6B3AD4 = a1; dword_6B36B0 = a2; dword_6B3D04 = a3; if (dword_51EBD8 & 4) dword_6B3D04 = 2 * a3; dword_6B403F = a4; dword_6B3CF4 = a6; dword_6B400E = a5; dword_6B403B = a7; dword_6B3CF0 = a6 + a5; dword_6B3D08 = a8; if (a7) dword_6B4012 = a6 / a7; else dword_6B4012 = 1; dword_51EE0C = 0; dword_6B3680 = a9; } // 0x4F49F0 void _MVE_sfCallbacks(void (*fn)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int)) { _sf_ShowFrame = fn; } // 0x4F4A00 void _do_nothing_2(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { } // 0x4F4A10 void movieLibSetPaletteEntriesProc(void (*fn)(unsigned char*, int, int)) { _pal_SetPalette = fn; } // 0x4F4B50 int _sub_4F4B5() { return 0; } // 0x4F4B80 void movieLibSetDirectDraw(LPDIRECTDRAW dd) { gMovieLibDirectDraw = dd; } // 0x4F4B90 void _MVE_rmCallbacks(int (*fn)()) { _rm_ctl = fn; } // 0x4F4BB0 void _sub_4F4BB(int a1) { if (a1 == 3) { dword_51EBDC = 3; } else { dword_51EBDC = 4; } } // 0x4F4BD0 void _MVE_rmFrameCounts(int* a1, int* a2) { *a1 = _rm_FrameCount; *a2 = _rm_FrameDropCount; } // 0x4F4BF0 int _MVE_rmPrepMovie(int fileHandle, int a2, int a3, char a4) { _sub_4F4DD(); if (gMovieLibDirectDraw == NULL) { return -11; } _rm_dx = a2; _rm_dy = a3; _rm_track_bit = 1 << a4; if (_rm_track_bit == 0) { _rm_track_bit = 1; } if (!_ioReset(fileHandle)) { _MVE_rmEndMovie(); return -8; } _rm_p = _ioNextRecord(); _rm_len = 0; if (!_rm_p) { _MVE_rmEndMovie(); return -2; } _rm_active = 1; _rm_hold = 0; _rm_FrameCount = 0; _rm_FrameDropCount = 0; return 0; } // 0x4F4C90 int _ioReset(int stream) { Mve* mve; _io_handle = stream; mve = (Mve*)_ioRead(sizeof(Mve)); if (mve == NULL) { return 0; } if (strncmp(mve->sig, "Interplay MVE File\x1A\x00", 20) != 0) { return 0; } if (~mve->field_16 - mve->field_18 != 0xFFFFEDCC) { return 0; } if (mve->field_16 != 256) { return 0; } if (mve->field_14 != 26) { return 0; } _io_next_hdr = mve->field_1A; return 1; } // Reads data from movie file. // // 0x4F4D00 void* _ioRead(int size) { void* buf; buf = _MVE_MemAlloc(&_io_mem_buf, size); if (buf == NULL) { return NULL; } return gMovieLibReadProc(_io_handle, buf, size) < 1 ? NULL : buf; } // 0x4F4D40 void* _MVE_MemAlloc(STRUCT_6B3690* a1, unsigned int a2) { void* ptr; if (a1->field_4 >= a2) { return a1->field_0; } if (gMovieLibMallocProc == NULL) { return NULL; } _MVE_MemFree(a1); ptr = gMovieLibMallocProc(a2 + 100); if (ptr == NULL) { return NULL; } _MVE_MemInit(a1, a2 + 100, ptr); a1->field_8 = 1; return a1->field_0; } // 0x4F4DA0 unsigned char* _ioNextRecord() { unsigned char* buf; buf = (unsigned char*)_ioRead((_io_next_hdr & 0xFFFF) + 4); if (buf == NULL) { return NULL; } _io_next_hdr = *(int*)(buf + (_io_next_hdr & 0xFFFF)); return buf; } // 0x4F4DD0 void _sub_4F4DD() { if (dword_51EE20) { return; } // TODO: Incomplete. dword_51EE20 = true; } // 0x4F4E20 int _MVE_rmHoldMovie() { if (!_rm_hold) { _MVE_sndPause(); _rm_hold = 1; } _syncWait(); return 0; } // 0x4F4E40 int _syncWait() { int result; result = 0; if (_sync_active) { if (((_sync_time + 1000 * timeGetTime()) & 0x80000000) != 0) { result = 1; while (((_sync_time + 1000 * timeGetTime()) & 0x80000000) != 0) ; } _sync_time += _sync_wait_quanta; } return result; } // 0x4F4EA0 void _MVE_sndPause() { if (gMovieLibDirectSoundBuffer != NULL) { IDirectSoundBuffer_Stop(gMovieLibDirectSoundBuffer); } } // 0x4F4EC0 int _MVE_rmStepMovie() { int v0; unsigned short* v1; unsigned int v5; int v6; int v7; int v8; int v9; int v10; int v11; int v12; int v13; unsigned short* v3; unsigned short* v21; unsigned short v22; int v18; int v19; int v20; unsigned char* v14; v0 = _rm_len; v1 = (unsigned short*)_rm_p; if (!_rm_active) { return -10; } if (_rm_hold) { _MVE_sndResume(); _rm_hold = 0; } LABEL_5: v21 = NULL; v3 = NULL; if (!v1) { v6 = -2; _MVE_rmEndMovie(); return v6; } while (1) { v5 = *(unsigned int*)((unsigned char*)v1 + v0); v1 = (unsigned short*)((unsigned char*)v1 + v0 + 4); v0 = v5 & 0xFFFF; switch ((v5 >> 16) & 0xFF) { case 0: return -1; case 1: v0 = 0; v1 = (unsigned short*)_ioNextRecord(); goto LABEL_5; case 2: if (!_syncInit(v1[0], v1[2])) { v6 = -3; break; } continue; case 3: if ((v5 >> 24) < 1) { v7 = 0; } else { v7 = (v1[1] & 0x04) >> 2; } v8 = *(unsigned int*)((unsigned char*)v1 + 6); if ((v5 >> 24) == 0) { v8 &= 0xFFFF; } if (_MVE_sndConfigure(v1[0], v8, v1[1] & 0x01, v1[2], (v1[1] & 0x02) >> 1, v7)) { continue; } v6 = -4; break; case 4: // initialize audio buffers _MVE_sndSync(); continue; case 5: v9 = 0; if ((v5 >> 24) >= 2) { v9 = v1[3]; } v10 = 1; if ((v5 >> 24) >= 1) { v10 = v1[2]; } if (!_nfConfig(v1[0], v1[1], v10, v9)) { v6 = -5; break; } v11 = 4 * _mveBW / dword_51EBDC & 0xFFFFFFF0; if (dword_6B4027) { v11 >>= 1; } v12 = _rm_dx; if (v12 < 0) { v12 = 0; } if (v11 + v12 > _sf_ScreenWidth) { v6 = -6; break; } v13 = _rm_dy; if (v13 < 0) { v13 = 0; } if (_mveBH + v13 > _sf_ScreenHeight) { v6 = -6; break; } if (dword_6B4027 && !dword_6B3680) { v6 = -6; break; } continue; case 7: ++_rm_FrameCount; v18 = 0; if ((v5 >> 24) >= 1) { v18 = v1[2]; } v19 = v1[1]; if (v19 == 0 || v21 || dword_6B3680) { _SetPalette_1(v1[0], v19); } else { _SetPalette_(v1[0], v19); } if (v21) { _do_nothing_(_rm_dx, _rm_dy, v21); } else if (!_sync_late || v1[1]) { _sfShowFrame(_rm_dx, _rm_dy, v18); } else { _sync_FrameDropped = 1; ++_rm_FrameDropCount; } v20 = v1[1]; if (v20 && !v21 && !dword_6B3680) { _SetPalette_1(v1[0], v20); } _rm_p = (unsigned char*)v1; _rm_len = v0; return 0; case 8: case 9: // push data to audio buffers? if (v1[1] & _rm_track_bit) { v14 = (unsigned char*)v1 + 6; if ((v5 >> 16) != 8) { v14 = NULL; } _CallsSndBuff_Loc(v14, v1[2]); } continue; case 10: if (!dword_51EE0C) { continue; } // TODO: Probably never reached. continue; case 11: // some kind of palette rotation _palMakeSynthPalette(v1[0], v1[1], v1[2], v1[3], v1[4], v1[5]); continue; case 12: // palette _palLoadPalette((unsigned char*)v1 + 4, v1[0], v1[1]); continue; case 14: // save current position v21 = v1; continue; case 15: // save current position v3 = v1; continue; case 17: // decode video chunk if ((v5 >> 24) < 3) { v6 = -8; break; } // swap movie surfaces if (v1[6] & 0x01) { movieSwapSurfaces(); } if (dword_6B4027) { if (dword_51EBD8) { v6 = -8; break; } // lock if (!movieLockSurfaces()) { v6 = -12; break; } // TODO: Incomplete. assert(false); // _nfHPkDecomp(v3, v1[7], v1[2], v1[3], v1[4], v1[5]); // unlock movieUnlockSurfaces(); continue; } if ((dword_51EBD8 & 3) == 1) { // lock if (!movieLockSurfaces()) { v6 = -12; break; } // TODO: Incomplete. assert(false); // _nfPkDecompH(v3, v1[7], v1[2], v1[3], v1[4], v1[5]); // unlock movieUnlockSurfaces(); continue; } if ((dword_51EBD8 & 3) == 2) { // lock if (!movieLockSurfaces()) { v6 = -12; break; } // TODO: Incomplete. assert(false); // _nfPkDecompH(v3, v1[7], v1[2], v1[3], v1[4], v1[5]); // unlock movieUnlockSurfaces(); continue; } // lock if (!movieLockSurfaces()) { v6 = -12; break; } _nfPkDecomp((unsigned char*)v3, (unsigned char*)&v1[7], v1[2], v1[3], v1[4], v1[5]); // unlock movieUnlockSurfaces(); continue; default: // unknown chunk continue; } } _MVE_rmEndMovie(); return v6; } // 0x4F54F0 int _syncInit(int a1, int a2) { int v2; v2 = -((a2 >> 1) + a1 * a2); if (_sync_active && _sync_wait_quanta == v2) { return 1; } _syncWait(); _sync_wait_quanta = v2; _syncReset(v2); return 1; } // 0x4F5540 void _syncReset(int a1) { _sync_active = 1; _sync_time = -1000 * timeGetTime() + a1; } // 0x4F5570 int _MVE_sndConfigure(int a1, int a2, int a3, int a4, int a5, int a6) { DSBUFFERDESC dsbd; WAVEFORMATEX wfxFormat; if (gMovieLibDirectSound == NULL) { return 1; } _MVE_sndReset(); _snd_comp = a3; dword_6B36A0 = a5; _snd_buf = a6; dsbd.dwSize = sizeof(DSBUFFERDESC); dsbd.dwFlags = DSBCAPS_CTRLFREQUENCY | DSBCAPS_CTRLPAN | DSBCAPS_CTRLVOLUME; dsbd.dwBufferBytes = (a2 + (a2 >> 1)) & 0xFFFFFFFC; dsbd.dwReserved = 0; dsbd.lpwfxFormat = &wfxFormat; wfxFormat.wFormatTag = 1; wfxFormat.nSamplesPerSec = a4; wfxFormat.nChannels = 2 - (a3 < 1); wfxFormat.nBlockAlign = wfxFormat.nChannels * (2 - (a5 < 1)); wfxFormat.cbSize = 0; wfxFormat.nAvgBytesPerSec = wfxFormat.nSamplesPerSec * wfxFormat.nBlockAlign; wfxFormat.wBitsPerSample = a5 < 1 ? 8 : 16; dword_6B3AE4 = 0; dword_6B3660 = 0; if (IDirectSound_CreateSoundBuffer(gMovieLibDirectSound, &dsbd, &gMovieLibDirectSoundBuffer, NULL) != DS_OK) { return 0; } IDirectSoundBuffer_SetVolume(gMovieLibDirectSoundBuffer, gMovieLibVolume); IDirectSoundBuffer_SetPan(gMovieLibDirectSoundBuffer, gMovieLibPan); dword_6B36A4 = 0; stru_6B3668.dwSize = sizeof(DSBCAPS); if (IDirectSoundBuffer_GetCaps(gMovieLibDirectSoundBuffer, &stru_6B3668) != DS_OK) { return 0; } return 1; } // 0x4F56C0 void _MVE_syncSync() { if (_sync_active) { while (((_sync_time + 1000 * timeGetTime()) & 0x80000000) != 0) { } } } // 0x4F56F0 void _MVE_sndReset() { if (gMovieLibDirectSoundBuffer != NULL) { IDirectSoundBuffer_Stop(gMovieLibDirectSoundBuffer); IDirectSoundBuffer_Release(gMovieLibDirectSoundBuffer); gMovieLibDirectSoundBuffer = NULL; } } // 0x4F5720 void _MVE_sndSync() { DWORD dwCurrentPlayCursor; DWORD dwCurrentWriteCursor; bool v10; DWORD dwStatus; int v1; bool v2; int v3; int v4; bool v5; bool v0; int v6; int v7; int v8; int v9; v0 = false; _sync_late = _syncWaitLevel(_sync_wait_quanta >> 2) > -_sync_wait_quanta >> 1 && !_sync_FrameDropped; _sync_FrameDropped = 0; if (gMovieLibDirectSound == NULL) { return; } if (gMovieLibDirectSoundBuffer == NULL) { return; } while (1) { if (IDirectSoundBuffer_GetStatus(gMovieLibDirectSoundBuffer, &dwStatus) != DS_OK) { return; } if (IDirectSoundBuffer_GetCurrentPosition(gMovieLibDirectSoundBuffer, &dwCurrentPlayCursor, &dwCurrentWriteCursor) != DS_OK) { return; } dwCurrentWriteCursor = dword_6B36A4; v1 = (stru_6B3668.dwBufferBytes + dword_6B39E0[dword_6B3660] - _gSoundTimeBase) % stru_6B3668.dwBufferBytes; if (dwCurrentPlayCursor <= dword_6B36A4) { if (v1 < dwCurrentPlayCursor || v1 >= dword_6B36A4) { v2 = false; } else { v2 = true; } } else { if (v1 < dwCurrentPlayCursor && v1 >= dword_6B36A4) { v2 = false; } else { v2 = true; } } if (!v2 || !(dwStatus & DSBSTATUS_PLAYING)) { if (v0) { _syncReset(_sync_wait_quanta + (_sync_wait_quanta >> 2)); } v3 = dword_6B39E0[dword_6B3660]; if (!(dwStatus & DSBSTATUS_PLAYING)) { v4 = (stru_6B3668.dwBufferBytes + v3) % stru_6B3668.dwBufferBytes; if (dwCurrentWriteCursor >= dwCurrentPlayCursor) { if (v4 >= dwCurrentPlayCursor && v4 < dwCurrentWriteCursor) { v5 = true; } else { v5 = false; } } else if (v4 >= dwCurrentPlayCursor || v4 < dwCurrentWriteCursor) { v5 = true; } else { v5 = false; } if (v5) { if (IDirectSoundBuffer_SetCurrentPosition(gMovieLibDirectSoundBuffer, v4) != DS_OK) { return; } if (IDirectSoundBuffer_Play(gMovieLibDirectSoundBuffer, 0, 0, 1) != DS_OK) { return; } } break; } v6 = (stru_6B3668.dwBufferBytes + _gSoundTimeBase + v3) % stru_6B3668.dwBufferBytes; v7 = dwCurrentWriteCursor - dwCurrentPlayCursor; if (((dwCurrentWriteCursor - dwCurrentPlayCursor) & 0x80000000) != 0) { v7 += stru_6B3668.dwBufferBytes; } v8 = stru_6B3668.dwBufferBytes - v7 - 1; if (stru_6B3668.dwBufferBytes / 2 < v8) { v8 = stru_6B3668.dwBufferBytes >> 1; } v9 = (stru_6B3668.dwBufferBytes + dwCurrentPlayCursor - v8) % stru_6B3668.dwBufferBytes; dwCurrentPlayCursor = v9; if (dwCurrentWriteCursor >= v9) { if (v6 < dwCurrentPlayCursor || v6 >= dwCurrentWriteCursor) { v10 = false; } else { v10 = true; } } else { if (v6 >= v9 || v6 < dwCurrentWriteCursor) { v10 = true; } else { v10 = false; } } if (!v10) { IDirectSoundBuffer_Stop(gMovieLibDirectSoundBuffer); } break; } v0 = true; } if (dword_6B3660 != dword_6B3AE4) { if (dword_6B3660 == 59) { dword_6B3660 = 0; } else { ++dword_6B3660; } } } // 0x4F59B0 int _syncWaitLevel(int a1) { int v2; int result; if (!_sync_active) { return 0; } v2 = _sync_time + a1; do { result = v2 + 1000 * timeGetTime(); } while (result < 0); _sync_time += _sync_wait_quanta; return result; } // 0x4F5A00 void _CallsSndBuff_Loc(unsigned char* a1, int a2) { int v2; int v3; int v5; DWORD dwCurrentPlayCursor; DWORD dwCurrentWriteCursor; LPVOID lpvAudioPtr1; DWORD dwAudioBytes1; LPVOID lpvAudioPtr2; DWORD dwAudioBytes2; _gSoundTimeBase = a2; if (gMovieLibDirectSoundBuffer == NULL) { return; } v5 = 60; if (dword_6B3660) { v5 = dword_6B3660; } if (dword_6B3AE4 - v5 == -1) { return; } if (IDirectSoundBuffer_GetCurrentPosition(gMovieLibDirectSoundBuffer, &dwCurrentPlayCursor, &dwCurrentWriteCursor) != DS_OK) { return; } dwCurrentWriteCursor = dword_6B36A4; if (IDirectSoundBuffer_Lock(gMovieLibDirectSoundBuffer, dword_6B36A4, a2, &lpvAudioPtr1, &dwAudioBytes1, &lpvAudioPtr2, &dwAudioBytes2, 0) != DS_OK) { return; } v2 = 0; v3 = 1; if (dwAudioBytes1 != 0) { v2 = _MVE_sndAdd((unsigned char*)lpvAudioPtr1, &a1, dwAudioBytes1, 0, 1); v3 = 0; dword_6B36A4 += dwAudioBytes1; } if (dwAudioBytes2 != 0) { _MVE_sndAdd((unsigned char*)lpvAudioPtr2, &a1, dwAudioBytes2, v2, v3); dword_6B36A4 = dwAudioBytes2; } if (dword_6B36A4 == stru_6B3668.dwBufferBytes) { dword_6B36A4 = 0; } IDirectSoundBuffer_Unlock(gMovieLibDirectSoundBuffer, lpvAudioPtr1, dwAudioBytes1, lpvAudioPtr2, dwAudioBytes2); dword_6B39E0[dword_6B3AE4] = dwCurrentWriteCursor; if (dword_6B3AE4 == 59) { dword_6B3AE4 = 0; } else { ++dword_6B3AE4; } } // 0x4F5B70 int _MVE_sndAdd(unsigned char* dest, unsigned char** src_ptr, int a3, int a4, int a5) { unsigned char* src; int v9; unsigned short* v10; int v11; int result; int v12; unsigned short* v13; int v14; src = *src_ptr; if (*src_ptr == NULL) { memset(dest, dword_6B36A0 < 1 ? 0x80 : 0, a3); *src_ptr = NULL; return a4; } if (!_snd_buf) { memcpy(dest, src_ptr, a3); *src_ptr += a3; return a4; } if (!_snd_comp) { if (a5) { v9 = *(unsigned short*)src; src += 2; *(unsigned short*)dest = v9; v10 = (unsigned short*)(dest + 2); v11 = a3 - 2; } else { v9 = a4; v10 = (unsigned short*)dest; v11 = a3; } result = _MVE_sndDecompM16(v10, src, v11 >> 1, v9); *src_ptr = src + (v11 >> 1); return result; } if (a5) { v12 = *(unsigned int*)src; src += 4; *(unsigned int*)dest = v12; v13 = (unsigned short*)(dest + 4); v14 = a3 - 4; } else { v13 = (unsigned short*)dest; v14 = a3; v12 = a4; } result = _MVE_sndDecompS16(v13, src, v14 >> 2, v12); *src_ptr = src + (v14 >> 1); return result; } // 0x4F5CA0 void _MVE_sndResume() { } // 0x4F5CB0 int _nfConfig(int a1, int a2, int a3, int a4) { DDSURFACEDESC ddsd; if (gMovieDirectDrawSurface1 != NULL) { IDirectDrawSurface_Release(gMovieDirectDrawSurface1); gMovieDirectDrawSurface1 = NULL; } if (gMovieDirectDrawSurface2 != NULL) { IDirectDrawSurface_Release(gMovieDirectDrawSurface2); gMovieDirectDrawSurface2 = NULL; } byte_6B400D = a1; byte_6B400C = a2; byte_6B4016 = a3; _mveBW = 8 * a1; _mveBH = 8 * a2 * a3; if (dword_51EBD8) { _mveBH >>= 1; } memset(&ddsd, 0, sizeof(DDSURFACEDESC)); ddsd.dwSize = sizeof(DDSURFACEDESC); ddsd.dwFlags = (DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT); ddsd.dwWidth = _mveBW; ddsd.dwHeight = _mveBH; ddsd.ddsCaps.dwCaps = (DDSCAPS_SYSTEMMEMORY | DDSCAPS_OFFSCREENPLAIN); ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); if (a4) { ddsd.ddpfPixelFormat.dwFlags = 64; ddsd.ddpfPixelFormat.dwRGBBitCount = 16; ddsd.ddpfPixelFormat.dwRBitMask = 0x7C00; ddsd.ddpfPixelFormat.dwGBitMask = 0x3E0; ddsd.ddpfPixelFormat.dwBBitMask = 0x1F; } else { ddsd.ddpfPixelFormat.dwFlags = 96; ddsd.ddpfPixelFormat.dwRGBBitCount = 8; } if (IDirectDraw_CreateSurface(gMovieLibDirectDraw, &ddsd, &gMovieDirectDrawSurface1, NULL) != DD_OK) { return 0; } if (IDirectDraw_CreateSurface(gMovieLibDirectDraw, &ddsd, &gMovieDirectDrawSurface2, NULL) != DD_OK) { return 0; } dword_6B4027 = a4; dword_6B402B = a3 * _mveBW - 8; if (a4) { _mveBW *= 2; dword_6B402B *= 2; } dword_6B3D00 = 8 * a3 * _mveBW; dword_6B3CEC = 7 * a3 * _mveBW; _nfPkConfig(); return 1; } // 0x4F5E60 bool movieLockSurfaces() { DDSURFACEDESC ddsd; ddsd.dwSize = sizeof(DDSURFACEDESC); if (gMovieDirectDrawSurface1 != NULL && gMovieDirectDrawSurface2 != NULL) { if (IDirectDrawSurface_Lock(gMovieDirectDrawSurface1, NULL, &ddsd, 0, NULL) != DD_OK) { return false; } gMovieDirectDrawSurfaceBuffer1 = (unsigned char*)ddsd.lpSurface; if (IDirectDrawSurface_Lock(gMovieDirectDrawSurface2, NULL, &ddsd, 0, NULL) != DD_OK) { return false; } gMovieDirectDrawSurfaceBuffer2 = (unsigned char*)ddsd.lpSurface; } return true; } // 0x4F5EF0 void movieUnlockSurfaces() { IDirectDrawSurface_Unlock(gMovieDirectDrawSurface1, NULL); IDirectDrawSurface_Unlock(gMovieDirectDrawSurface2, NULL); } // 0x4F5F20 void movieSwapSurfaces() { LPDIRECTDRAWSURFACE tmp = gMovieDirectDrawSurface2; gMovieDirectDrawSurface2 = gMovieDirectDrawSurface1; gMovieDirectDrawSurface1 = tmp; } // 0x4F5F40 void _sfShowFrame(int a1, int a2, int a3) { int v3; int v4; int v5; int v6; int v7; v4 = ((4 * _mveBW / dword_51EBDC - 12) & 0xFFFFFFF0) + 12; dword_6B3CF8 = _mveBW - dword_51EBDC * (v4 >> 2); v3 = a1; if (a1 < 0) { if (dword_6B4027) { v3 = (_sf_ScreenWidth - (v4 >> 1)) >> 1; } else { v3 = (_sf_ScreenWidth - v4) >> 1; } } if (dword_6B4027) { v3 *= 2; } v5 = a2; if (a2 >= 0) { v6 = _mveBH; } else { v6 = _mveBH; if (dword_51EBD8 & 4) { v5 = (_sf_ScreenHeight - 2 * _mveBH) >> 1; } else { v5 = (_sf_ScreenHeight - _mveBH) >> 1; } } v7 = v3 & 0xFFFFFFFC; if (dword_51EBD8 & 4) { v5 >>= 1; } if (a3) { // TODO: Incomplete. // _mve_ShowFrameField(off_6B4033, _mveBW, v6, dword_6B401B, dword_6B401F, dword_6B4017, dword_6B4023, v7, v5, a3); } else if (dword_51EBDC == 4) { _sf_ShowFrame(gMovieDirectDrawSurface1, _mveBW, v6, dword_6B401B, dword_6B401F, dword_6B4017, dword_6B4023, v7, v5); } else { _sf_ShowFrame(gMovieDirectDrawSurface1, _mveBW, v6, 0, dword_6B401F, ((4 * _mveBW / dword_51EBDC - 12) & 0xFFFFFFF0) + 12, dword_6B4023, v7, v5); } } // 0x4F6080 void _do_nothing_(int a1, int a2, unsigned short* a3) { } // 0x4F6090 void _SetPalette_1(int a1, int a2) { if (!dword_6B4027) { _pal_SetPalette(_pal_tbl, a1, a2); } } // 0x4F60C0 void _SetPalette_(int a1, int a2) { if (!dword_6B4027) { _pal_SetPalette(_palette_entries1, a1, a2); } } // 0x4F60F0 void _palMakeSynthPalette(int a1, int a2, int a3, int a4, int a5, int a6) { int i; int j; for (i = 0; i < a2; i++) { for (j = 0; j < a3; j++) { _pal_tbl[3 * a1 + 3 * j] = (63 * i) / (a2 - 1); _pal_tbl[3 * a1 + 3 * j + 1] = 0; _pal_tbl[3 * a1 + 3 * j + 2] = 5 * ((63 * j) / (a3 - 1)) / 8; } } for (i = 0; i < a5; i++) { for (j = 0; j < a6; j++) { _pal_tbl[3 * a4 + 3 * j] = 0; _pal_tbl[3 * a4 + 3 * j + 1] = (63 * i) / (a5 - 1); _pal_tbl[3 * a1 + 3 * j + 2] = 5 * ((63 * j) / (a6 - 1)) / 8; } } } // 0x4F6210 void _palLoadPalette(unsigned char* palette, int a2, int a3) { memcpy(_pal_tbl + 3 * a2, palette, 3 * a3); } // 0x4F6240 void _MVE_rmEndMovie() { if (_rm_active) { _syncWait(); _syncRelease(); _MVE_sndReset(); _rm_active = 0; } } // 0x4F6270 void _syncRelease() { _sync_active = 0; } // 0x4F6350 void _MVE_ReleaseMem() { _MVE_rmEndMovie(); _ioRelease(); _MVE_sndRelease(); _nfRelease(); } // 0x4F6370 void _ioRelease() { _MVE_MemFree(&_io_mem_buf); } // 0x4F6380 void _MVE_sndRelease() { } // 0x4F6390 void _nfRelease() { if (gMovieDirectDrawSurface1 != NULL) { IDirectDrawSurface_Release(gMovieDirectDrawSurface1); gMovieDirectDrawSurface1 = NULL; } if (gMovieDirectDrawSurface2 != NULL) { IDirectDrawSurface_Release(gMovieDirectDrawSurface2); gMovieDirectDrawSurface2 = NULL; } } // 0x4F6550 void _frLoad(STRUCT_4F6930* a1) { gMovieLibReadProc = a1->readProc; _io_mem_buf.field_0 = a1->field_8.field_0; _io_mem_buf.field_4 = a1->field_8.field_4; _io_mem_buf.field_8 = a1->field_8.field_8; _io_handle = a1->fileHandle; _io_next_hdr = a1->field_18; gMovieDirectDrawSurface1 = a1->field_24; gMovieDirectDrawSurface2 = a1->field_28; dword_6B3AE8 = a1->field_2C; gMovieDirectDrawSurfaceBuffer1 = a1->field_30; gMovieDirectDrawSurfaceBuffer2 = a1->field_34; byte_6B400D = a1->field_38; byte_6B400C = a1->field_39; byte_6B4016 = a1->field_3A; dword_6B4027 = a1->field_3C; _mveBW = a1->field_40; _mveBH = a1->field_44; dword_6B402B = a1->field_48; dword_6B3D00 = a1->field_4C; dword_6B3CEC = a1->field_50; } // 0x4F6610 void _frSave(STRUCT_4F6930* a1) { STRUCT_6B3690* ptr; ptr = &(a1->field_8); a1->readProc = gMovieLibReadProc; ptr->field_0 = _io_mem_buf.field_0; ptr->field_4 = _io_mem_buf.field_4; ptr->field_8 = _io_mem_buf.field_8; a1->fileHandle = _io_handle; a1->field_18 = _io_next_hdr; a1->field_24 = gMovieDirectDrawSurface1; a1->field_28 = gMovieDirectDrawSurface2; a1->field_2C = dword_6B3AE8; a1->field_30 = gMovieDirectDrawSurfaceBuffer1; a1->field_34 = gMovieDirectDrawSurfaceBuffer2; a1->field_38 = byte_6B400D; a1->field_39 = byte_6B400C; a1->field_3A = byte_6B4016; a1->field_3C = dword_6B4027; a1->field_40 = _mveBW; a1->field_44 = _mveBH; a1->field_48 = dword_6B402B; a1->field_4C = dword_6B3D00; a1->field_50 = dword_6B3CEC; } // 0x4F6930 void _MVE_frClose(STRUCT_4F6930* a1) { STRUCT_4F6930 v1; _frSave(&v1); _frLoad(a1); _ioRelease(); _nfRelease(); _frLoad(&v1); if (gMovieLibFreeProc != NULL) { gMovieLibFreeProc(a1); } } // 0x4F697C int _MVE_sndDecompM16(unsigned short* a1, unsigned char* a2, int a3, int a4) { int i; int v8; unsigned short result; result = a4; v8 = 0; for (i = 0; i < a3; i++) { v8 = *a2++; result += word_51EBE0[v8]; *a1++ = result; } return result; } // 0x4F69AD int _MVE_sndDecompS16(unsigned short* a1, unsigned char* a2, int a3, int a4) { int i; unsigned short v4; unsigned short v5; unsigned short v9; v4 = a4 & 0xFFFF; v5 = (a4 >> 16) & 0xFFFF; v9 = 0; for (i = 0; i < a3; i++) { v9 = *a2++; v4 = (word_51EBE0[v9] + v4) & 0xFFFF; *a1++ = v4; v9 = *a2++; v5 = (word_51EBE0[v9] + v5) & 0xFFFF; *a1++ = v5; } return (v5 << 16) | v4; } // 0x4F731D void _nfPkConfig() { int* ptr; int v1; int v2; int v3; int v4; int v5; ptr = dword_51F018; v1 = _mveBW; v2 = 0; v3 = 128; do { *ptr++ = v2; v2 += v1; --v3; } while (v3); v4 = -128 * v1; v5 = 128; do { *ptr++ = v4; v4 += v1; --v5; } while (v5); } // 0x4F7359 void _nfPkDecomp(unsigned char* a1, unsigned char* a2, int a3, int a4, int a5, int a6) { int v49; unsigned char* dest; int v8; int v7; int i; int j; int v10; int v11; int v13; int byte; unsigned int value1; unsigned int value2; int var_10; unsigned char map1[512]; unsigned int map2[256]; int var_8; unsigned int* src_ptr; unsigned int* dest_ptr; unsigned int nibbles[2]; dword_6B401B = 8 * a3; dword_6B4017 = 8 * a5; dword_6B401F = 8 * a4 * byte_6B4016; dword_6B4023 = 8 * a6 * byte_6B4016; var_8 = dword_6B3D00 - dword_6B4017; dest = gMovieDirectDrawSurfaceBuffer1; var_10 = dword_6B3CEC - 8; if (a3 || a4) { dest = gMovieDirectDrawSurfaceBuffer1 + dword_6B401B + _mveBW * dword_6B401F; } while (a6--) { v49 = a5 >> 1; while (v49--) { v8 = *a1++; nibbles[0] = v8 & 0xF; nibbles[1] = v8 >> 4; for (j = 0; j < 2; j++) { v7 = nibbles[j]; switch (v7) { case 1: dest += 8; break; case 0: case 2: case 3: case 4: case 5: switch (v7) { case 0: v10 = gMovieDirectDrawSurfaceBuffer2 - gMovieDirectDrawSurfaceBuffer1; break; case 2: case 3: byte = *a2++; v11 = word_51F618[byte]; if (v7 == 3) { v11 = ((-(v11 & 0xFF)) & 0xFF) | ((-(v11 >> 8) & 0xFF) << 8); } else { v11 = v11; } v10 = ((v11 << 24) >> 24) + dword_51F018[v11 >> 8]; break; case 4: case 5: if (v7 == 4) { byte = *a2++; v13 = word_51F418[byte]; } else { v13 = *(unsigned short*)a2; a2 += 2; } v10 = ((v13 << 24) >> 24) + dword_51F018[v13 >> 8] + (gMovieDirectDrawSurfaceBuffer2 - gMovieDirectDrawSurfaceBuffer1); break; } value2 = _mveBW; for (i = 0; i < 8; i++) { src_ptr = (unsigned int*)(dest + v10); dest_ptr = (unsigned int*)dest; dest_ptr[0] = src_ptr[0]; dest_ptr[1] = src_ptr[1]; dest += value2; } dest -= value2; dest -= var_10; break; case 6: nibbles[0] += 2; while (nibbles[0]--) { dest += 16; if (v49--) { continue; } dest += var_8; a6--; v49 = (a5 >> 1) - 1; } break; case 7: if (a2[0] > a2[1]) { // 7/1 for (i = 0; i < 2; i++) { value1 = _$$R0053[a2[2 + i] & 0xF]; map1[i * 8] = value1 & 0xFF; map1[i * 8 + 1] = (value1 >> 8) & 0xFF; map1[i * 8 + 2] = (value1 >> 16) & 0xFF; map1[i * 8 + 3] = (value1 >> 24) & 0xFF; value1 = _$$R0053[a2[2 + i] >> 4]; map1[i * 8 + 4] = value1 & 0xFF; map1[i * 8 + 5] = (value1 >> 8) & 0xFF; map1[i * 8 + 6] = (value1 >> 16) & 0xFF; map1[i * 8 + 7] = (value1 >> 24) & 0xFF; } map2[0xC1] = (a2[1] << 8) | a2[1]; // cx map2[0xC3] = (a2[0] << 8) | a2[0]; // bx value2 = _mveBW; for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4]] << 16) | (map2[map1[i * 4 + 1]]); dest_ptr = (unsigned int*)(dest + value2); dest_ptr[0] = (map2[map1[i * 4]] << 16) | (map2[map1[i * 4 + 1]]); dest_ptr = (unsigned int*)dest; dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]]); dest_ptr = (unsigned int*)(dest + value2); dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]]); dest += value2 * 2; } dest -= value2; a2 += 4; dest -= var_10; } else { // 7/2 // VERIFIED for (i = 0; i < 8; i++) { value1 = _$$R0004[a2[2 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } map2[0xC1] = (a2[1] << 8) | a2[0]; // cx map2[0xC3] = (a2[0] << 8) | a2[0]; // bx map2[0xC2] = (a2[0] << 8) | a2[1]; // dx map2[0xC5] = (a2[1] << 8) | a2[1]; // bp value2 = _mveBW; for (i = 0; i < 8; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]]; dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]]; dest += value2; } dest -= value2; a2 += 10; dest -= var_10; } break; case 8: if (a2[0] > a2[1]) { if (a2[6] > a2[7]) { // 8/1 for (i = 0; i < 4; i++) { value1 = _$$R0004[a2[2 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 4; i++) { value1 = _$$R0004[a2[8 + i]]; map1[16 + i * 4] = value1 & 0xFF; map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF; } value2 = _mveBW; map2[0xC1] = (a2[1] << 8) | a2[0]; // cx map2[0xC3] = (a2[0] << 8) | a2[0]; // bx map2[0xC2] = (a2[0] << 8) | a2[1]; // dx map2[0xC5] = (a2[1] << 8) | a2[1]; // bp for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]]; dest_ptr[1] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]]; dest += value2; } map2[0xC1] = (a2[6 + 1] << 8) | a2[6 + 0]; // cx map2[0xC3] = (a2[6 + 0] << 8) | a2[6 + 0]; // bx map2[0xC2] = (a2[6 + 0] << 8) | a2[6 + 1]; // dx map2[0xC5] = (a2[6 + 1] << 8) | a2[6 + 1]; // bp for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 4]] << 16) | map2[map1[16 + i * 4 + 1]]; dest_ptr[1] = (map2[map1[16 + i * 4 + 2]] << 16) | map2[map1[16 + i * 4 + 3]]; dest += value2; } dest -= value2; a2 += 12; dest -= var_10; } else { // 8/2 for (i = 0; i < 4; i++) { value1 = _$$R0004[a2[2 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 4; i++) { value1 = _$$R0004[a2[8 + i]]; map1[16 + i * 4] = value1 & 0xFF; map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF; } value2 = _mveBW; map2[0xC1] = (a2[1] << 8) | a2[0]; // cx map2[0xC3] = (a2[0] << 8) | a2[0]; // bx map2[0xC2] = (a2[0] << 8) | a2[1]; // dx map2[0xC5] = (a2[1] << 8) | a2[1]; // bp for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]]; dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]]; dest += value2; } dest -= value2 * 8 - 4; map2[0xC1] = (a2[6 + 1] << 8) | a2[6 + 0]; // cx map2[0xC3] = (a2[6 + 0] << 8) | a2[6 + 0]; // bx map2[0xC2] = (a2[6 + 0] << 8) | a2[6 + 1]; // dx map2[0xC5] = (a2[6 + 1] << 8) | a2[6 + 1]; // bp for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 4]] << 16) | map2[map1[16 + i * 4 + 1]]; dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 4 + 2]] << 16) | map2[map1[16 + i * 4 + 3]]; dest += value2; } dest -= value2; a2 += 12; dest -= 4; dest -= var_10; } } else { // 8/3 // VERIFIED for (i = 0; i < 2; i++) { value1 = _$$R0004[a2[2 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 2; i++) { value1 = _$$R0004[a2[6 + i]]; map1[8 + i * 4] = value1 & 0xFF; map1[8 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[8 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[8 + i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 2; i++) { value1 = _$$R0004[a2[10 + i]]; map1[16 + i * 4] = value1 & 0xFF; map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 2; i++) { value1 = _$$R0004[a2[14 + i]]; map1[24 + i * 4] = value1 & 0xFF; map1[24 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[24 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[24 + i * 4 + 3] = (value1 >> 24) & 0xFF; } value2 = _mveBW; map2[0xC1] = (a2[1] << 8) | a2[0]; // cx map2[0xC3] = (a2[0] << 8) | a2[0]; // bx map2[0xC2] = (a2[0] << 8) | a2[1]; // dx map2[0xC5] = (a2[1] << 8) | a2[1]; // bp for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4]] << 16) | map2[map1[i * 4 + 1]]; dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4 + 2]] << 16) | map2[map1[i * 4 + 3]]; dest += value2; } map2[0xC1] = (a2[4 + 1] << 8) | a2[4 + 0]; // cx map2[0xC3] = (a2[4 + 0] << 8) | a2[4 + 0]; // bx map2[0xC2] = (a2[4 + 0] << 8) | a2[4 + 1]; // dx map2[0xC5] = (a2[4 + 1] << 8) | a2[4 + 1]; // bp for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[8 + i * 4]] << 16) | map2[map1[8 + i * 4 + 1]]; dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[8 + i * 4 + 2]] << 16) | map2[map1[8 + i * 4 + 3]]; dest += value2; } dest -= value2 * 8 - 4; map2[0xC1] = (a2[8 + 1] << 8) | a2[8 + 0]; // cx map2[0xC3] = (a2[8 + 0] << 8) | a2[8 + 0]; // bx map2[0xC2] = (a2[8 + 0] << 8) | a2[8 + 1]; // dx map2[0xC5] = (a2[8 + 1] << 8) | a2[8 + 1]; // bp for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 4]] << 16) | map2[map1[16 + i * 4 + 1]]; dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 4 + 2]] << 16) | map2[map1[16 + i * 4 + 3]]; dest += value2; } map2[0xC1] = (a2[12 + 1] << 8) | a2[12 + 0]; // cx map2[0xC3] = (a2[12 + 0] << 8) | a2[12 + 0]; // bx map2[0xC2] = (a2[12 + 0] << 8) | a2[12 + 1]; // dx map2[0xC5] = (a2[12 + 1] << 8) | a2[12 + 1]; // bp for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[24 + i * 4]] << 16) | map2[map1[24 + i * 4 + 1]]; dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[24 + i * 4 + 2]] << 16) | map2[map1[24 + i * 4 + 3]]; dest += value2; } dest -= value2; a2 += 16; dest -= 4; dest -= var_10; } break; case 9: if (a2[0] > a2[1]) { if (a2[2] > a2[3]) { // 9/1 // VERIFIED for (i = 0; i < 8; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh value2 = _mveBW; for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8); dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8); dest_ptr = (unsigned int*)(dest + value2); dest_ptr[0] = (map2[map1[i * 8]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8); dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8); dest += value2 * 2; } dest -= value2; a2 += 12; dest -= var_10; } else { // 9/2 // VERIFIED for (i = 0; i < 8; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4 + 3] = value1 & 0xFF; map1[i * 4 + 2] = (value1 >> 8) & 0xFF; map1[i * 4 + 1] = (value1 >> 16) & 0xFF; map1[i * 4] = (value1 >> 24) & 0xFF; } map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh value2 = _mveBW; for (i = 0; i < 8; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4]] << 24) | (map2[map1[i * 4 + 0]] << 16) | (map2[map1[i * 4 + 1]] << 8) | (map2[map1[i * 4 + 1]]); dest_ptr[1] = (map2[map1[i * 4 + 2]] << 24) | (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]] << 8) | (map2[map1[i * 4 + 3]]); dest += value2; } dest -= value2; a2 += 12; dest -= var_10; } } else { if (a2[2] > a2[3]) { // 9/3 // VERIFIED for (i = 0; i < 4; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4 + 3] = value1 & 0xFF; map1[i * 4 + 2] = (value1 >> 8) & 0xFF; map1[i * 4 + 1] = (value1 >> 16) & 0xFF; map1[i * 4] = (value1 >> 24) & 0xFF; } map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh value2 = _mveBW; for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4 + 0]] << 24) | (map2[map1[i * 4 + 0]] << 16) | (map2[map1[i * 4 + 1]] << 8) | (map2[map1[i * 4 + 1]]); dest_ptr[1] = (map2[map1[i * 4 + 2]] << 24) | (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]] << 8) | (map2[map1[i * 4 + 3]]); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 4 + 0]] << 24) | (map2[map1[i * 4 + 0]] << 16) | (map2[map1[i * 4 + 1]] << 8) | (map2[map1[i * 4 + 1]]); dest_ptr[1] = (map2[map1[i * 4 + 2]] << 24) | (map2[map1[i * 4 + 2]] << 16) | (map2[map1[i * 4 + 3]] << 8) | (map2[map1[i * 4 + 3]]); dest += value2; } dest -= value2; a2 += 8; dest -= var_10; } else { // 9/4 // VERIFIED for (i = 0; i < 16; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh value2 = _mveBW; for (i = 0; i < 8; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8); dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8); dest += value2; } dest -= value2; a2 += 20; dest -= var_10; } } break; case 10: if (a2[0] > a2[1]) { if (a2[12] > a2[13]) { // 10/1 // VERIFIED for (i = 0; i < 8; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 8; i++) { value1 = _$$R0063[a2[16 + i]]; map1[32 + i * 4] = value1 & 0xFF; map1[32 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[32 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[32 + i * 4 + 3] = (value1 >> 24) & 0xFF; } value2 = _mveBW; map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8); dest_ptr[1] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8); dest += value2; } map2[0xC1] = a2[0x0C + 2]; // mov al, cl map2[0xC3] = a2[0x0C + 0]; // mov al, bl map2[0xC5] = a2[0x0C + 3]; // mov al, ch map2[0xC7] = a2[0x0C + 1]; // mov al, bh map2[0xE1] = a2[0x0C + 2]; // mov ah, cl map2[0xE3] = a2[0x0C + 0]; // mov ah, bl map2[0xE5] = a2[0x0C + 3]; // mov ah, ch map2[0xE7] = a2[0x0C + 1]; // mov ah, bh for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[32 + i * 8 + 0]] << 16) | (map2[map1[32 + i * 8 + 1]] << 24) | (map2[map1[32 + i * 8 + 2]]) | (map2[map1[32 + i * 8 + 3]] << 8); dest_ptr[1] = (map2[map1[32 + i * 8 + 4]] << 16) | (map2[map1[32 + i * 8 + 5]] << 24) | (map2[map1[32 + i * 8 + 6]]) | (map2[map1[32 + i * 8 + 7]] << 8); dest += value2; } dest -= value2; a2 += 24; dest -= var_10; } else { // 10/2 // VERIFIED for (i = 0; i < 8; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 8; i++) { value1 = _$$R0063[a2[16 + i]]; map1[32 + i * 4] = value1 & 0xFF; map1[32 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[32 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[32 + i * 4 + 3] = (value1 >> 24) & 0xFF; } value2 = _mveBW; map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8); dest += value2; } dest -= value2 * 8 - 4; map2[0xC1] = a2[0x0C + 2]; // mov al, cl map2[0xC3] = a2[0x0C + 0]; // mov al, bl map2[0xC5] = a2[0x0C + 3]; // mov al, ch map2[0xC7] = a2[0x0C + 1]; // mov al, bh map2[0xE1] = a2[0x0C + 2]; // mov ah, cl map2[0xE3] = a2[0x0C + 0]; // mov ah, bl map2[0xE5] = a2[0x0C + 3]; // mov ah, ch map2[0xE7] = a2[0x0C + 1]; // mov ah, bh for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[32 + i * 8 + 0]] << 16) | (map2[map1[32 + i * 8 + 1]] << 24) | (map2[map1[32 + i * 8 + 2]]) | (map2[map1[32 + i * 8 + 3]] << 8); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[32 + i * 8 + 4]] << 16) | (map2[map1[32 + i * 8 + 5]] << 24) | (map2[map1[32 + i * 8 + 6]]) | (map2[map1[32 + i * 8 + 7]] << 8); dest += value2; } dest -= value2; a2 += 24; dest -= 4; dest -= var_10; } } else { // 10/3 // VERIFIED for (i = 0; i < 4; i++) { value1 = _$$R0063[a2[4 + i]]; map1[i * 4] = value1 & 0xFF; map1[i * 4 + 1] = (value1 >> 8) & 0xFF; map1[i * 4 + 2] = (value1 >> 16) & 0xFF; map1[i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 4; i++) { value1 = _$$R0063[a2[12 + i]]; map1[16 + i * 4] = value1 & 0xFF; map1[16 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[16 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[16 + i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 4; i++) { value1 = _$$R0063[a2[20 + i]]; map1[32 + i * 4] = value1 & 0xFF; map1[32 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[32 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[32 + i * 4 + 3] = (value1 >> 24) & 0xFF; } for (i = 0; i < 4; i++) { value1 = _$$R0063[a2[28 + i]]; map1[48 + i * 4] = value1 & 0xFF; map1[48 + i * 4 + 1] = (value1 >> 8) & 0xFF; map1[48 + i * 4 + 2] = (value1 >> 16) & 0xFF; map1[48 + i * 4 + 3] = (value1 >> 24) & 0xFF; } value2 = _mveBW; map2[0xC1] = a2[2]; // mov al, cl map2[0xC3] = a2[0]; // mov al, bl map2[0xC5] = a2[3]; // mov al, ch map2[0xC7] = a2[1]; // mov al, bh map2[0xE1] = a2[2]; // mov ah, cl map2[0xE3] = a2[0]; // mov ah, bl map2[0xE5] = a2[3]; // mov ah, ch map2[0xE7] = a2[1]; // mov ah, bh for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8 + 0]] << 16) | (map2[map1[i * 8 + 1]] << 24) | (map2[map1[i * 8 + 2]]) | (map2[map1[i * 8 + 3]] << 8); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[i * 8 + 4]] << 16) | (map2[map1[i * 8 + 5]] << 24) | (map2[map1[i * 8 + 6]]) | (map2[map1[i * 8 + 7]] << 8); dest += value2; } map2[0xC1] = a2[0x08 + 2]; // mov al, cl map2[0xC3] = a2[0x08 + 0]; // mov al, bl map2[0xC5] = a2[0x08 + 3]; // mov al, ch map2[0xC7] = a2[0x08 + 1]; // mov al, bh map2[0xE1] = a2[0x08 + 2]; // mov ah, cl map2[0xE3] = a2[0x08 + 0]; // mov ah, bl map2[0xE5] = a2[0x08 + 3]; // mov ah, ch map2[0xE7] = a2[0x08 + 1]; // mov ah, bh for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 8 + 0]] << 16) | (map2[map1[16 + i * 8 + 1]] << 24) | (map2[map1[16 + i * 8 + 2]]) | (map2[map1[16 + i * 8 + 3]] << 8); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[16 + i * 8 + 4]] << 16) | (map2[map1[16 + i * 8 + 5]] << 24) | (map2[map1[16 + i * 8 + 6]]) | (map2[map1[16 + i * 8 + 7]] << 8); dest += value2; } dest -= value2 * 8 - 4; map2[0xC1] = a2[0x10 + 2]; // mov al, cl map2[0xC3] = a2[0x10 + 0]; // mov al, bl map2[0xC5] = a2[0x10 + 3]; // mov al, ch map2[0xC7] = a2[0x10 + 1]; // mov al, bh map2[0xE1] = a2[0x10 + 2]; // mov ah, cl map2[0xE3] = a2[0x10 + 0]; // mov ah, bl map2[0xE5] = a2[0x10 + 3]; // mov ah, ch map2[0xE7] = a2[0x10 + 1]; // mov ah, bh for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[32 + i * 8 + 0]] << 16) | (map2[map1[32 + i * 8 + 1]] << 24) | (map2[map1[32 + i * 8 + 2]]) | (map2[map1[32 + i * 8 + 3]] << 8); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[32 + i * 8 + 4]] << 16) | (map2[map1[32 + i * 8 + 5]] << 24) | (map2[map1[32 + i * 8 + 6]]) | (map2[map1[32 + i * 8 + 7]] << 8); dest += value2; } map2[0xC1] = a2[0x18 + 2]; // mov al, cl map2[0xC3] = a2[0x18 + 0]; // mov al, bl map2[0xC5] = a2[0x18 + 3]; // mov al, ch map2[0xC7] = a2[0x18 + 1]; // mov al, bh map2[0xE1] = a2[0x18 + 2]; // mov ah, cl map2[0xE3] = a2[0x18 + 0]; // mov ah, bl map2[0xE5] = a2[0x18 + 3]; // mov ah, ch map2[0xE7] = a2[0x18 + 1]; // mov ah, bh for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[48 + i * 8 + 0]] << 16) | (map2[map1[48 + i * 8 + 1]] << 24) | (map2[map1[48 + i * 8 + 2]]) | (map2[map1[48 + i * 8 + 3]] << 8); dest += value2; dest_ptr = (unsigned int*)dest; dest_ptr[0] = (map2[map1[48 + i * 8 + 4]] << 16) | (map2[map1[48 + i * 8 + 5]] << 24) | (map2[map1[48 + i * 8 + 6]]) | (map2[map1[48 + i * 8 + 7]] << 8); dest += value2; } dest -= value2; a2 += 32; dest -= 4; dest -= var_10; } break; case 11: value2 = _mveBW; src_ptr = (unsigned int*)a2; for (i = 0; i < 8; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = src_ptr[i * 2]; dest_ptr[1] = src_ptr[i * 2 + 1]; dest += value2; } dest -= value2; a2 += 64; dest -= var_10; break; case 12: value2 = _mveBW; for (i = 0; i < 4; i++) { byte = a2[i * 4 + 0]; value1 = byte | (byte << 8); byte = a2[i * 4 + 1]; value1 |= (byte << 16) | (byte << 24); byte = a2[i * 4 + 2]; value2 = byte | (byte << 8); byte = a2[i * 4 + 3]; value2 |= (byte << 16) | (byte << 24); dest_ptr = (unsigned int*)dest; dest_ptr[0] = value1; dest_ptr[1] = value2; dest_ptr = (unsigned int*)(dest + _mveBW); dest_ptr[0] = value1; dest_ptr[1] = value2; dest += _mveBW * 2; } dest -= _mveBW; a2 += 16; dest -= var_10; break; case 13: byte = a2[0]; value1 = byte | (byte << 8) | (byte << 16) | (byte << 24); byte = a2[1]; value2 = byte | (byte << 8) | (byte << 16) | (byte << 24); for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = value1; dest_ptr[1] = value2; dest_ptr = (unsigned int*)(dest + _mveBW); dest_ptr[0] = value1; dest_ptr[1] = value2; dest += _mveBW * 2; } byte = a2[2]; value1 = byte | (byte << 8) | (byte << 16) | (byte << 24); byte = a2[3]; value2 = byte | (byte << 8) | (byte << 16) | (byte << 24); for (i = 0; i < 2; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = value1; dest_ptr[1] = value2; dest_ptr = (unsigned int*)(dest + _mveBW); dest_ptr[0] = value1; dest_ptr[1] = value2; dest += _mveBW * 2; } dest -= _mveBW; a2 += 4; dest -= var_10; break; case 14: case 15: if (v7 == 14) { byte = *a2++; value1 = byte | (byte << 8) | (byte << 16) | (byte << 24); value2 = value1; } else { byte = *(unsigned short*)a2; a2 += 2; value1 = byte | (byte << 16); value2 = value1; value2 = _rotl(value2, 8); } for (i = 0; i < 4; i++) { dest_ptr = (unsigned int*)dest; dest_ptr[0] = value1; dest_ptr[1] = value1; dest += _mveBW; dest_ptr = (unsigned int*)dest; dest_ptr[0] = value2; dest_ptr[1] = value2; dest += _mveBW; } dest -= _mveBW; dest -= var_10; break; } } } dest += var_8; } } ================================================ FILE: src/movie_lib.h ================================================ #ifndef MOVIE_LIB_H #define MOVIE_LIB_H #define WIN32_LEAN_AND_MEAN #include #define DIRECTDRAW_VERSION 0x0300 #include #include #define DIRECTSOUND_VERSION 0x0300 #include #include #include "memory_defs.h" typedef struct STRUCT_6B3690 { void* field_0; int field_4; int field_8; } STRUCT_6B3690; #pragma pack(2) typedef struct Mve { char sig[20]; short field_14; short field_16; short field_18; int field_1A; } Mve; #pragma pack() typedef bool MovieReadProc(int fileHandle, void* buffer, int count); typedef struct STRUCT_4F6930 { int field_0; MovieReadProc* readProc; STRUCT_6B3690 field_8; int fileHandle; int field_18; LPDIRECTDRAWSURFACE field_24; LPDIRECTDRAWSURFACE field_28; int field_2C; unsigned char* field_30; unsigned char* field_34; unsigned char field_38; unsigned char field_39; unsigned char field_3A; unsigned char field_3B; int field_3C; int field_40; int field_44; int field_48; int field_4C; int field_50; } STRUCT_4F6930; extern int dword_51EBD8; extern int dword_51EBDC; extern unsigned short word_51EBE0[256]; extern LPDIRECTDRAW gMovieLibDirectDraw; extern int _sync_active; extern int _sync_late; extern int _sync_FrameDropped; extern LPDIRECTSOUND gMovieLibDirectSound; extern LPDIRECTSOUNDBUFFER gMovieLibDirectSoundBuffer; extern int gMovieLibVolume; extern int gMovieLibPan; extern LPDIRECTDRAWSURFACE gMovieDirectDrawSurface1; extern LPDIRECTDRAWSURFACE gMovieDirectDrawSurface2; extern void (*_sf_ShowFrame)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int); extern int dword_51EE0C; extern void (*_pal_SetPalette)(unsigned char*, int, int); extern int _rm_hold; extern int _rm_active; extern bool dword_51EE20; extern int dword_51F018[256]; extern unsigned short word_51F418[256]; extern unsigned short word_51F618[256]; extern unsigned int _$$R0053[16]; extern unsigned int _$$R0004[256]; extern unsigned int _$$R0063[256]; extern int dword_6B3660; extern DSBCAPS stru_6B3668; extern int _sf_ScreenWidth; extern int dword_6B3680; extern int _rm_FrameDropCount; extern int _snd_buf; extern STRUCT_6B3690 _io_mem_buf; extern int _io_next_hdr; extern int dword_6B36A0; extern int dword_6B36A4; extern int _rm_FrameCount; extern int _sf_ScreenHeight; extern int dword_6B36B0; extern unsigned char _palette_entries1[768]; extern MallocProc* gMovieLibMallocProc; extern int (*_rm_ctl)(); extern int _rm_dx; extern int _rm_dy; extern int _gSoundTimeBase; extern int _io_handle; extern int _rm_len; extern FreeProc* gMovieLibFreeProc; extern int _snd_comp; extern unsigned char* _rm_p; extern int dword_6B39E0[60]; extern int _sync_wait_quanta; extern int dword_6B3AD4; extern int _rm_track_bit; extern int _sync_time; extern MovieReadProc* gMovieLibReadProc; extern int dword_6B3AE4; extern int dword_6B3AE8; extern int dword_6B3CEC; extern int dword_6B3CF0; extern int dword_6B3CF4; extern int dword_6B3CF8; extern int _mveBW; extern int dword_6B3D00; extern int dword_6B3D04; extern int dword_6B3D08; extern unsigned char _pal_tbl[768]; extern unsigned char byte_6B400C; extern unsigned char byte_6B400D; extern int dword_6B400E; extern int dword_6B4012; extern unsigned char byte_6B4016; extern int dword_6B4017; extern int dword_6B401B; extern int dword_6B401F; extern int dword_6B4023; extern int dword_6B4027; extern int dword_6B402B; extern int _mveBH; extern unsigned char* gMovieDirectDrawSurfaceBuffer1; extern unsigned char* gMovieDirectDrawSurfaceBuffer2; extern int dword_6B403B; extern int dword_6B403F; void movieLibSetMemoryProcs(MallocProc* mallocProc, FreeProc* freeProc); void movieLibSetReadProc(MovieReadProc* readProc); void _MVE_MemInit(STRUCT_6B3690* a1, int a2, void* a3); void _MVE_MemFree(STRUCT_6B3690* a1); void movieLibSetDirectSound(LPDIRECTSOUND ds); void movieLibSetVolume(int volume); void movieLibSetPan(int pan); void _MVE_sfSVGA(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); void _MVE_sfCallbacks(void (*fn)(LPDIRECTDRAWSURFACE, int, int, int, int, int, int, int, int)); void _do_nothing_2(LPDIRECTDRAWSURFACE a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9); void movieLibSetPaletteEntriesProc(void (*fn)(unsigned char*, int, int)); int _sub_4F4B5(); void movieLibSetDirectDraw(LPDIRECTDRAW dd); void _MVE_rmCallbacks(int (*fn)()); void _sub_4F4BB(int a1); void _MVE_rmFrameCounts(int* a1, int* a2); int _MVE_rmPrepMovie(int fileHandle, int a2, int a3, char a4); int _ioReset(int fileHandle); void* _ioRead(int size); void* _MVE_MemAlloc(STRUCT_6B3690* a1, unsigned int a2); unsigned char* _ioNextRecord(); void _sub_4F4DD(); int _MVE_rmHoldMovie(); int _syncWait(); void _MVE_sndPause(); int _MVE_rmStepMovie(); int _syncInit(int a1, int a2); void _syncReset(int a1); int _MVE_sndConfigure(int a1, int a2, int a3, int a4, int a5, int a6); void _MVE_syncSync(); void _MVE_sndReset(); void _MVE_sndSync(); int _syncWaitLevel(int a1); void _CallsSndBuff_Loc(unsigned char* a1, int a2); int _MVE_sndAdd(unsigned char* dest, unsigned char** src_ptr, int a3, int a4, int a5); void _MVE_sndResume(); int _nfConfig(int a1, int a2, int a3, int a4); bool movieLockSurfaces(); void movieUnlockSurfaces(); void movieSwapSurfaces(); void _sfShowFrame(int a1, int a2, int a3); void _do_nothing_(int a1, int a2, unsigned short* a3); void _SetPalette_1(int a1, int a2); void _SetPalette_(int a1, int a2); void _palMakeSynthPalette(int a1, int a2, int a3, int a4, int a5, int a6); void _palLoadPalette(unsigned char* palette, int a2, int a3); void _MVE_rmEndMovie(); void _syncRelease(); void _MVE_ReleaseMem(); void _ioRelease(); void _MVE_sndRelease(); void _nfRelease(); void _frLoad(STRUCT_4F6930* a1); void _frSave(STRUCT_4F6930* a1); void _MVE_frClose(STRUCT_4F6930* a1); int _MVE_sndDecompM16(unsigned short* a1, unsigned char* a2, int a3, int a4); int _MVE_sndDecompS16(unsigned short* a1, unsigned char* a2, int a3, int a4); void _nfPkConfig(); void _nfPkDecomp(unsigned char* buf, unsigned char* a2, int a3, int a4, int a5, int a6); #endif /* MOVIE_LIB_H */ ================================================ FILE: src/plib/assoc/assoc.c ================================================ #include "plib/assoc/assoc.h" #include #include #include #include // NOTE: I guess this marker is used as a type discriminator for implementing // nested dictionaries. That's why every dictionary-related function starts // with a check for this value. #define DICTIONARY_MARKER 0xFEBAFEBA static void* default_malloc(size_t t); static void* default_realloc(void* p, size_t t); static void default_free(void* p); static int assoc_find(assoc_array* a, const char* name, int* position); static int assoc_read_long(FILE* fp, long* theLong); static int assoc_read_assoc_array(FILE* fp, assoc_array* a); static int assoc_write_long(FILE* fp, long theLong); static int assoc_write_assoc_array(FILE* fp, assoc_array* a); // 0x51E408 static assoc_malloc_func* internal_malloc = default_malloc; // 0x51E40C static assoc_realloc_func* internal_realloc = default_realloc; // 0x51E410 static assoc_free_func* internal_free = default_free; // 0x4D9B90 static void* default_malloc(size_t t) { return malloc(t); } // 0x4D9B98 static void* default_realloc(void* p, size_t t) { return realloc(p, t); } // 0x4D9BA0 static void default_free(void* p) { free(p); } // 0x4D9BA8 int assoc_init(assoc_array* a, int n, size_t datasize, assoc_func_list* assoc_funcs) { a->max = n; a->datasize = datasize; a->size = 0; if (assoc_funcs != NULL) { memcpy(&(a->load_save_funcs), assoc_funcs, sizeof(*assoc_funcs)); } else { a->load_save_funcs.loadFunc = NULL; a->load_save_funcs.saveFunc = NULL; a->load_save_funcs.loadFuncDB = NULL; a->load_save_funcs.saveFuncDB = NULL; } int rc = 0; if (n != 0) { a->list = (assoc_pair*)internal_malloc(sizeof(*a->list) * n); if (a->list == NULL) { rc = -1; } } else { a->list = NULL; } if (rc != -1) { a->init_flag = DICTIONARY_MARKER; } return rc; } // 0x4D9C0C int assoc_resize(assoc_array* a, int n) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } if (n < a->size) { return -1; } assoc_pair* entries = (assoc_pair*)internal_realloc(a->list, sizeof(*a->list) * n); if (entries == NULL) { return -1; } a->max = n; a->list = entries; return 0; } // 0x4D9C48 int assoc_free(assoc_array* a) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } for (int index = 0; index < a->size; index++) { assoc_pair* entry = &(a->list[index]); if (entry->name != NULL) { internal_free(entry->name); } if (entry->data != NULL) { internal_free(entry->data); } } if (a->list != NULL) { internal_free(a->list); } memset(a, 0, sizeof(*a)); return 0; } // Finds index for the given key. // // Returns 0 if key is found. Otherwise returns -1, in this case [indexPtr] // specifies an insertion point for given key. // // 0x4D9CC4 static int assoc_find(assoc_array* a, const char* name, int* position) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } if (a->size == 0) { *position = 0; return -1; } int r = a->size - 1; int l = 0; int mid = 0; int cmp = 0; while (r >= l) { mid = (l + r) / 2; cmp = stricmp(name, a->list[mid].name); if (cmp == 0) { break; } if (cmp > 0) { l = l + 1; } else { r = r - 1; } } if (cmp == 0) { *position = mid; return 0; } if (cmp < 0) { *position = mid; } else { *position = mid + 1; } return -1; } // Returns the index of the entry for the specified key, or -1 if it's not // present in the dictionary. // // 0x4D9D5C int assoc_search(assoc_array* a, const char* name) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } int index; if (assoc_find(a, name, &index) != 0) { return -1; } return index; } // Adds key-value pair to the dictionary if the specified key is not already // present. // // Returns 0 on success, or -1 on any error (including key already exists // error). // // 0x4D9D88 int assoc_insert(assoc_array* a, const char* name, const void* data) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } int newElementIndex; if (assoc_find(a, name, &newElementIndex) == 0) { // Element for this key is already exists. return -1; } if (a->size == a->max) { // assoc array reached it's capacity and needs to be enlarged. if (assoc_resize(a, 2 * (a->max + 1)) == -1) { return -1; } } // Make a copy of the key. char* keyCopy = (char*)internal_malloc(strlen(name) + 1); if (keyCopy == NULL) { return -1; } strcpy(keyCopy, name); // Make a copy of the value. void* valueCopy = NULL; if (data != NULL && a->datasize != 0) { valueCopy = internal_malloc(a->datasize); if (valueCopy == NULL) { internal_free(keyCopy); return -1; } } if (valueCopy != NULL && a->datasize != 0) { memcpy(valueCopy, data, a->datasize); } // Starting at the end of entries array loop backwards and move entries down // one by one until we reach insertion point. for (int index = a->size; index > newElementIndex; index--) { assoc_pair* src = &(a->list[index - 1]); assoc_pair* dest = &(a->list[index]); memcpy(dest, src, sizeof(*a->list)); } assoc_pair* entry = &(a->list[newElementIndex]); entry->name = keyCopy; entry->data = valueCopy; a->size++; return 0; } // Removes key-value pair from the dictionary if specified key is present in // the dictionary. // // Returns 0 on success, -1 on any error (including key not present error). // // 0x4D9EE8 int assoc_delete(assoc_array* a, const char* name) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } int indexToRemove; if (assoc_find(a, name, &indexToRemove) == -1) { return -1; } assoc_pair* entry = &(a->list[indexToRemove]); // Free key and value (which are copies). internal_free(entry->name); if (entry->data != NULL) { internal_free(entry->data); } a->size--; // Starting from the index of the entry we've just removed, loop thru the // remaining of the array and move entries up one by one. for (int index = indexToRemove; index < a->size; index++) { assoc_pair* src = &(a->list[index + 1]); assoc_pair* dest = &(a->list[index]); memcpy(dest, src, sizeof(*a->list)); } return 0; } // NOTE: Unused. // // 0x4D9F84 int assoc_copy(assoc_array* dst, assoc_array* src) { if (src->init_flag != DICTIONARY_MARKER) { return -1; } if (assoc_init(dst, src->max, src->datasize, &(src->load_save_funcs)) != 0) { // FIXME: Should return -1, as we were unable to initialize dictionary. return 0; } for (int index = 0; index < src->size; index++) { assoc_pair* entry = &(src->list[index]); if (assoc_insert(dst, entry->name, entry->data) == -1) { return -1; } } return 0; } // NOTE: Unused. // // 0x4DA090 static int assoc_read_long(FILE* fp, long* theLong) { int c; int temp; c = fgetc(fp); if (c == -1) { return -1; } temp = (c & 0xFF); c = fgetc(fp); if (c == -1) { return -1; } temp = (temp << 8) | (c & 0xFF); c = fgetc(fp); if (c == -1) { return -1; } temp = (temp << 8) | (c & 0xFF); c = fgetc(fp); if (c == -1) { return -1; } temp = (temp << 8) | (c & 0xFF); *theLong = temp; return 0; } // NOTE: Unused. // // 0x4DA0F4 static int assoc_read_assoc_array(FILE* fp, assoc_array* a) { long temp; if (assoc_read_long(fp, &temp) != 0) return -1; a->size = temp; if (assoc_read_long(fp, &temp) != 0) return -1; a->max = temp; if (assoc_read_long(fp, &temp) != 0) return -1; a->datasize = temp; if (assoc_read_long(fp, &temp) != 0) return -1; // FIXME: Reading pointer. a->list = (assoc_pair*)temp; return 0; } // NOTE: Unused. // // 0x4DA158 int assoc_load(FILE* fp, assoc_array* a, int flags) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } for (int index = 0; index < a->size; index++) { assoc_pair* entry = &(a->list[index]); if (entry->name != NULL) { internal_free(entry->name); } if (entry->data != NULL) { internal_free(entry->data); } } if (a->list != NULL) { internal_free(a->list); } if (assoc_read_assoc_array(fp, a) != 0) { return -1; } a->list = NULL; if (a->max <= 0) { return 0; } a->list = (assoc_pair*)internal_malloc(sizeof(*a->list) * a->max); if (a->list == NULL) { return -1; } for (int index = 0; index < a->size; index++) { assoc_pair* entry = &(a->list[index]); entry->name = NULL; entry->data = NULL; } if (a->size <= 0) { return 0; } for (int index = 0; index < a->size; index++) { assoc_pair* entry = &(a->list[index]); int keyLength = fgetc(fp); if (keyLength == -1) { return -1; } entry->name = (char*)internal_malloc(keyLength + 1); if (entry->name == NULL) { return -1; } if (fgets(entry->name, keyLength, fp) == NULL) { return -1; } if (a->datasize != 0) { entry->data = internal_malloc(a->datasize); if (entry->data == NULL) { return -1; } if (a->load_save_funcs.loadFunc != NULL) { if (a->load_save_funcs.loadFunc(fp, entry->data, a->datasize, flags) != 0) { return -1; } } else { if (fread(entry->data, a->datasize, 1, fp) != 1) { return -1; } } } } return 0; } // NOTE: Unused. // // 0x4DA2EC static int assoc_write_long(FILE* fp, long theLong) { if (fputc((theLong >> 24) & 0xFF, fp) == -1) return -1; if (fputc((theLong >> 16) & 0xFF, fp) == -1) return -1; if (fputc((theLong >> 8) & 0xFF, fp) == -1) return -1; if (fputc(theLong & 0xFF, fp) == -1) return -1; return 0; } // NOTE: Unused. // // 0x4DA360 static int assoc_write_assoc_array(FILE* fp, assoc_array* a) { if (assoc_write_long(fp, a->size) != 0) return -1; if (assoc_write_long(fp, a->max) != 0) return -1; if (assoc_write_long(fp, a->datasize) != 0) return -1; // FIXME: Writing pointer. if (assoc_write_long(fp, (int)a->list) != 0) return -1; return 0; } // NOTE: Unused. // // 0x4DA3A4 int assoc_save(FILE* fp, assoc_array* a, int flags) { if (a->init_flag != DICTIONARY_MARKER) { return -1; } if (assoc_write_assoc_array(fp, a) != 0) { return -1; } for (int index = 0; index < a->size; index++) { assoc_pair* entry = &(a->list[index]); int keyLength = strlen(entry->name); if (fputc(keyLength, fp) == -1) { return -1; } if (fputs(entry->name, fp) == -1) { return -1; } if (a->load_save_funcs.saveFunc != NULL) { if (a->datasize != 0) { if (a->load_save_funcs.saveFunc(fp, entry->data, a->datasize, flags) != 0) { return -1; } } } else { if (a->datasize != 0) { if (fwrite(entry->data, a->datasize, 1, fp) != 1) { return -1; } } } } return 0; } // 0x4DA498 void assoc_register_mem(assoc_malloc_func* malloc_func, assoc_realloc_func* realloc_func, assoc_free_func* free_func) { if (malloc_func != NULL && realloc_func != NULL && free_func != NULL) { internal_malloc = malloc_func; internal_realloc = realloc_func; internal_free = free_func; } else { internal_malloc = default_malloc; internal_realloc = default_realloc; internal_free = default_free; } } ================================================ FILE: src/plib/assoc/assoc.h ================================================ #ifndef FALLOUT_PLIB_ASSOC_ASSOC_H_ #define FALLOUT_PLIB_ASSOC_ASSOC_H_ #include typedef void*(assoc_malloc_func)(size_t size); typedef void*(assoc_realloc_func)(void* ptr, size_t newSize); typedef void(assoc_free_func)(void* ptr); typedef int(assoc_load_func)(FILE* stream, void* buffer, size_t size, int flags); typedef int(assoc_save_func)(FILE* stream, void* buffer, size_t size, int flags); // TODO: Check, probably wrong. typedef void(assoc_load_func_db)(); typedef void(assoc_save_func_db)(); typedef struct assoc_func_list { assoc_load_func* loadFunc; assoc_save_func* saveFunc; assoc_load_func_db* loadFuncDB; assoc_save_func_db* saveFuncDB; assoc_load_func* newLoadFunc; } assoc_func_list; // A tuple containing individual key-value pair of an assoc array. typedef struct assoc_pair { char* name; void* data; } assoc_pair; // A collection of key/value pairs. // // The keys in assoc array are always strings. Internally pairs are kept sorted // by the key. Both keys and values are copied when new entry is added to // assoc array. For this reason the size of the value's type is provided during // assoc array initialization. typedef struct assoc_array { int init_flag; // The number of key/value pairs in the array. int size; // The capacity of key/value pairs in [entries] array. int max; // The size of the values in bytes. size_t datasize; // IO callbacks. assoc_func_list load_save_funcs; // The array of key-value pairs. assoc_pair* list; } assoc_array; int assoc_init(assoc_array* a, int n, size_t datasize, assoc_func_list* assoc_funcs); int assoc_resize(assoc_array* a, int n); int assoc_free(assoc_array* a); int assoc_search(assoc_array* a, const char* name); int assoc_insert(assoc_array* a, const char* name, const void* data); int assoc_delete(assoc_array* a, const char* name); int assoc_copy(assoc_array* dst, assoc_array* src); int assoc_load(FILE* fp, assoc_array* a, int flags); int assoc_save(FILE* fp, assoc_array* a, int flags); void assoc_register_mem(assoc_malloc_func* malloc_func, assoc_realloc_func* realloc_func, assoc_free_func* free_func); #endif /* FALLOUT_PLIB_ASSOC_ASSOC_H_ */ ================================================ FILE: src/plib/color/color.c ================================================ #include "plib/color/color.h" #include #include #include "plib/gnw/input.h" #include "plib/gnw/svga.h" static int colorOpen(const char* filePath, int flags); static int colorRead(int fd, void* buffer, size_t size); static int colorClose(int fd); static void* defaultMalloc(size_t size); static void* defaultRealloc(void* ptr, size_t size); static void defaultFree(void* ptr); static void setIntensityTableColor(int a1); static void setIntensityTables(); static void setMixTableColor(int a1); static void setMixTable(); static void buildBlendTable(unsigned char* ptr, unsigned char ch); static void rebuildColorBlendTables(); static void maxfill(); // 0x50F930 static char _aColor_cNoError[] = "color.c: No errors\n"; // 0x50F95C static char _aColor_cColorTa[] = "color.c: color table not found\n"; // 0x50F984 static char _aColor_cColorpa[] = "color.c: colorpalettestack overflow"; // 0x50F9AC static char aColor_cColor_0[] = "color.c: colorpalettestack underflow"; // 0x51DF10 static char* errorStr = _aColor_cNoError; // 0x51DF14 static bool colorsInited = false; // 0x51DF18 static double currentGamma = 1.0; // 0x51DF20 static fade_bk_func* colorFadeBkFuncP = NULL; // 0x51DF24 static MallocProc* mallocPtr = defaultMalloc; // 0x51DF28 static ReallocProc* reallocPtr = defaultRealloc; // 0x51DF2C static FreeProc* freePtr = defaultFree; // 0x51DF30 static ColorNameMangleFunc* colorNameMangler = NULL; // 0x51DF34 unsigned char cmap[768] = { 0x3F, 0x3F, 0x3F }; // 0x673050 static ColorPaletteStackEntry* colorPaletteStack[COLOR_PALETTE_STACK_CAPACITY]; // 0x673090 static unsigned char systemCmap[256 * 3]; // 0x673390 static unsigned char currentGammaTable[64]; // 0x6733D0 static unsigned char* blendTable[256]; // 0x6737D0 unsigned char mappedColor[256]; // 0x6738D0 Color colorMixAddTable[256][256]; // 0x6838D0 Color intensityColorTable[256][256]; // 0x6938D0 Color colorMixMulTable[256][256]; // 0x6A38D0 unsigned char colorTable[32768]; // 0x6AB8D0 static int tos; // 0x6AB928 static ColorReadFunc* readFunc; // 0x6AB92C static ColorCloseFunc* closeFunc; // 0x6AB930 static ColorOpenFunc* openFunc; // NOTE: Inlined. // // 0x4C7200 static int colorOpen(const char* filePath, int flags) { if (openFunc != NULL) { return openFunc(filePath, flags); } return -1; } // NOTE: Inlined. // // 0x4C7218 static int colorRead(int fd, void* buffer, size_t size) { if (readFunc != NULL) { return readFunc(fd, buffer, size); } return -1; } // NOTE: Inlined. // // 0x4C7230 static int colorClose(int fd) { if (closeFunc != NULL) { return closeFunc(fd); } return -1; } // 0x4C7248 void colorInitIO(ColorOpenFunc* openProc, ColorReadFunc* readProc, ColorCloseFunc* closeProc) { openFunc = openProc; readFunc = readProc; closeFunc = closeProc; } // 0x4C725C static void* defaultMalloc(size_t size) { return malloc(size); } // 0x4C7264 static void* defaultRealloc(void* ptr, size_t size) { return realloc(ptr, size); } // 0x4C726C static void defaultFree(void* ptr) { free(ptr); } // 0x4C7274 void colorSetNameMangler(ColorNameMangleFunc* c) { colorNameMangler = c; } // 0x4C727C Color colorMixAdd(Color a, Color b) { return colorMixAddTable[a][b]; } // 0x4C7298 Color colorMixMul(Color a, Color b) { return colorMixMulTable[a][b]; } // 0x4C72B4 int calculateColor(int a1, int a2) { return intensityColorTable[a2][a1 >> 9]; } // 0x4C72CC Color RGB2Color(ColorRGB c) { return colorTable[c]; } // 0x4C72E0 int Color2RGB(int a1) { int v1, v2, v3; v1 = cmap[3 * a1] >> 1; v2 = cmap[3 * a1 + 1] >> 1; v3 = cmap[3 * a1 + 2] >> 1; return (((v1 << 5) | v2) << 5) | v3; } // Performs animated palette transition. // // 0x4C7320 void fadeSystemPalette(unsigned char* oldPalette, unsigned char* newPalette, int steps) { for (int step = 0; step < steps; step++) { unsigned char palette[768]; for (int index = 0; index < 768; index++) { palette[index] = oldPalette[index] - (oldPalette[index] - newPalette[index]) * step / steps; } if (colorFadeBkFuncP != NULL) { if (step % 128 == 0) { colorFadeBkFuncP(); } } setSystemPalette(palette); } setSystemPalette(newPalette); } // 0x4C73D4 void colorSetFadeBkFunc(fade_bk_func* callback) { colorFadeBkFuncP = callback; } // 0x4C73DC void setBlackSystemPalette() { // 0x6AB934 static unsigned char tmp[768]; setSystemPalette(tmp); } // 0x4C73E4 void setSystemPalette(unsigned char* palette) { unsigned char newPalette[768]; for (int index = 0; index < 768; index++) { newPalette[index] = currentGammaTable[palette[index]]; systemCmap[index] = palette[index]; } GNW95_SetPalette(newPalette); } // 0x4C7420 unsigned char* getSystemPalette() { return systemCmap; } // 0x4C7428 void setSystemPaletteEntries(unsigned char* palette, int start, int end) { unsigned char newPalette[768]; int length = end - start + 1; for (int index = 0; index < length; index++) { newPalette[index * 3] = currentGammaTable[palette[index * 3]]; newPalette[index * 3 + 1] = currentGammaTable[palette[index * 3 + 1]]; newPalette[index * 3 + 2] = currentGammaTable[palette[index * 3 + 2]]; systemCmap[start * 3 + index * 3] = palette[index * 3]; systemCmap[start * 3 + index * 3 + 1] = palette[index * 3 + 1]; systemCmap[start * 3 + index * 3 + 2] = palette[index * 3 + 2]; } GNW95_SetPaletteEntries(newPalette, start, end - start + 1); } // 0x4C74D0 void setSystemPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b) { int baseIndex; baseIndex = entry * 3; systemCmap[baseIndex] = r; systemCmap[baseIndex + 1] = g; systemCmap[baseIndex + 2] = b; GNW95_SetPaletteEntry(entry, currentGammaTable[r], currentGammaTable[g], currentGammaTable[b]); } // 0x4C752C void getSystemPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b) { int baseIndex; baseIndex = entry * 3; *r = systemCmap[baseIndex]; *g = systemCmap[baseIndex + 1]; *b = systemCmap[baseIndex + 2]; } // 0x4C7550 static void setIntensityTableColor(int a1) { int v1, v2, v3, v4, v5, v6, v7, v8, v9; v5 = 0; for (int index = 0; index < 128; index++) { v1 = (Color2RGB(a1) & 0x7C00) >> 10; v2 = (Color2RGB(a1) & 0x3E0) >> 5; v3 = (Color2RGB(a1) & 0x1F); v4 = (((v1 * v5) >> 16) << 10) | (((v2 * v5) >> 16) << 5) | ((v3 * v5) >> 16); intensityColorTable[a1][index] = colorTable[v4]; v6 = v1 + (((0x1F - v1) * v5) >> 16); v7 = v2 + (((0x1F - v2) * v5) >> 16); v8 = v3 + (((0x1F - v3) * v5) >> 16); v9 = (v6 << 10) | (v7 << 5) | v8; intensityColorTable[a1][0x7F + index + 1] = colorTable[v9]; v5 += 0x200; } } // 0x4C7658 static void setIntensityTables() { for (int index = 0; index < 256; index++) { if (mappedColor[index] != 0) { setIntensityTableColor(index); } else { memset(intensityColorTable[index], 0, 256); } } } // 0x4C769C static void setMixTableColor(int a1) { int i; int v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19; int v20, v21, v22, v23, v24, v25, v26, v27, v28, v29; for (i = 0; i < 256; i++) { if (mappedColor[a1] && mappedColor[i]) { v2 = (Color2RGB(a1) & 0x7C00) >> 10; v3 = (Color2RGB(a1) & 0x3E0) >> 5; v4 = (Color2RGB(a1) & 0x1F); v5 = (Color2RGB(i) & 0x7C00) >> 10; v6 = (Color2RGB(i) & 0x3E0) >> 5; v7 = (Color2RGB(i) & 0x1F); v8 = v2 + v5; v9 = v3 + v6; v10 = v4 + v7; v11 = v8; if (v9 > v11) { v11 = v9; } if (v10 > v11) { v11 = v10; } if (v11 <= 0x1F) { int paletteIndex = (v8 << 10) | (v9 << 5) | v10; v12 = colorTable[paletteIndex]; } else { v13 = v11 - 0x1F; v14 = v8 - v13; v15 = v9 - v13; v16 = v10 - v13; if (v14 < 0) { v14 = 0; } if (v15 < 0) { v15 = 0; } if (v16 < 0) { v16 = 0; } v17 = (v14 << 10) | (v15 << 5) | v16; v18 = colorTable[v17]; v19 = (int)((((double)v11 + (-31.0)) * 0.0078125 + 1.0) * 65536.0); v12 = calculateColor(v19, v18); } colorMixAddTable[a1][i] = v12; v20 = (Color2RGB(a1) & 0x7C00) >> 10; v21 = (Color2RGB(a1) & 0x3E0) >> 5; v22 = (Color2RGB(a1) & 0x1F); v23 = (Color2RGB(i) & 0x7C00) >> 10; v24 = (Color2RGB(i) & 0x3E0) >> 5; v25 = (Color2RGB(i) & 0x1F); v26 = (v20 * v23) >> 5; v27 = (v21 * v24) >> 5; v28 = (v22 * v25) >> 5; v29 = (v26 << 10) | (v27 << 5) | v28; colorMixMulTable[a1][i] = colorTable[v29]; } else { if (mappedColor[i]) { colorMixAddTable[a1][i] = i; colorMixMulTable[a1][i] = i; } else { colorMixAddTable[a1][i] = a1; colorMixMulTable[a1][i] = a1; } } } } // 0x4C78CC static void setMixTable() { int i; for (i = 0; i < 256; i++) { setMixTableColor(i); } } // 0x4C78E4 bool loadColorTable(const char* path) { if (colorNameMangler != NULL) { path = colorNameMangler(path); } // NOTE: Uninline. int fd = colorOpen(path, 0x200); if (fd == -1) { errorStr = _aColor_cColorTa; return false; } for (int index = 0; index < 256; index++) { unsigned char r; unsigned char g; unsigned char b; // NOTE: Uninline. colorRead(fd, &r, sizeof(r)); // NOTE: Uninline. colorRead(fd, &g, sizeof(g)); // NOTE: Uninline. colorRead(fd, &b, sizeof(b)); if (r <= 0x3F && g <= 0x3F && b <= 0x3F) { mappedColor[index] = 1; } else { r = 0; g = 0; b = 0; mappedColor[index] = 0; } cmap[index * 3] = r; cmap[index * 3 + 1] = g; cmap[index * 3 + 2] = b; } // NOTE: Uninline. colorRead(fd, colorTable, 0x8000); unsigned int type; // NOTE: Uninline. colorRead(fd, &type, sizeof(type)); // NOTE: The value is "NEWC". Original code uses cmp opcode, not stricmp, // or comparing characters one-by-one. if (type == 0x4E455743) { // NOTE: Uninline. colorRead(fd, intensityColorTable, 0x10000); // NOTE: Uninline. colorRead(fd, colorMixAddTable, 0x10000); // NOTE: Uninline. colorRead(fd, colorMixMulTable, 0x10000); } else { setIntensityTables(); for (int index = 0; index < 256; index++) { setMixTableColor(index); } } rebuildColorBlendTables(); // NOTE: Uninline. colorClose(fd); return true; } // 0x4C7AB4 char* colorError() { return errorStr; } // 0x4C7ABC void setColorPalette(unsigned char* pal) { memcpy(cmap, pal, sizeof(cmap)); memset(mappedColor, 1, sizeof(mappedColor)); } // 0x4C7AF8 void setColorPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b) { int baseIndex; baseIndex = entry * 3; cmap[baseIndex] = r; cmap[baseIndex + 1] = g; cmap[baseIndex + 2] = b; } // 0x4C7B20 void getColorPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b) { int baseIndex; baseIndex = entry * 3; *r = cmap[baseIndex]; *g = cmap[baseIndex + 1]; *b = cmap[baseIndex + 2]; } // 0x4C7B44 static void buildBlendTable(unsigned char* ptr, unsigned char ch) { int r, g, b; int i, j; int v12, v14, v16; unsigned char* beg; beg = ptr; r = (Color2RGB(ch) & 0x7C00) >> 10; g = (Color2RGB(ch) & 0x3E0) >> 5; b = (Color2RGB(ch) & 0x1F); for (i = 0; i < 256; i++) { ptr[i] = i; } ptr += 256; int b_1 = b; int v31 = 6; int g_1 = g; int r_1 = r; int b_2 = b_1; int g_2 = g_1; int r_2 = r_1; for (j = 0; j < 7; j++) { for (i = 0; i < 256; i++) { v12 = (Color2RGB(i) & 0x7C00) >> 10; v14 = (Color2RGB(i) & 0x3E0) >> 5; v16 = (Color2RGB(i) & 0x1F); int index = 0; index |= (r_2 + v12 * v31) / 7 << 10; index |= (g_2 + v14 * v31) / 7 << 5; index |= (b_2 + v16 * v31) / 7; ptr[i] = colorTable[index]; } v31--; ptr += 256; r_2 += r_1; g_2 += g_1; b_2 += b_1; } int v18 = 0; for (j = 0; j < 6; j++) { int v20 = v18 / 7 + 0xFFFF; for (i = 0; i < 256; i++) { ptr[i] = calculateColor(v20, ch); } v18 += 0x10000; ptr += 256; } } // 0x4C7D90 static void rebuildColorBlendTables() { int i; for (i = 0; i < 256; i++) { if (blendTable[i]) { buildBlendTable(blendTable[i], i); } } } // 0x4C7DC0 unsigned char* getColorBlendTable(int ch) { unsigned char* ptr; if (blendTable[ch] == NULL) { ptr = (unsigned char*)mallocPtr(4100); *(int*)ptr = 1; blendTable[ch] = ptr + 4; buildBlendTable(blendTable[ch], ch); } ptr = blendTable[ch]; *(int*)((unsigned char*)ptr - 4) = *(int*)((unsigned char*)ptr - 4) + 1; return ptr; } // 0x4C7E20 void freeColorBlendTable(int a1) { unsigned char* v2 = blendTable[a1]; if (v2 != NULL) { int* count = (int*)(v2 - sizeof(int)); *count -= 1; if (*count == 0) { freePtr(count); blendTable[a1] = NULL; } } } // 0x4C7E58 void colorRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc) { mallocPtr = mallocProc; reallocPtr = reallocProc; freePtr = freeProc; } // 0x4C7E6C void colorGamma(double value) { currentGamma = value; for (int i = 0; i < 64; i++) { double value = pow(i, currentGamma); currentGammaTable[i] = (unsigned char)min(max(value, 0.0), 63.0); } setSystemPalette(systemCmap); } // 0x4C7F0C double colorGetGamma() { return currentGamma; } // 0x4C7F14 int colorMappedColor(ColorIndex i) { return mappedColor[i]; } // 0x4C8804 static void maxfill(unsigned long* buffer, int side) { unsigned long maxv; long i; unsigned long* bp; bp = buffer; maxv = side * side * side; for (i = maxv; i > 0; i--) { *bp++ = -1; } } // NOTE: Unused. // // 0x4C8828 bool colorPushColorPalette() { if (tos >= COLOR_PALETTE_STACK_CAPACITY) { errorStr = _aColor_cColorpa; return false; } ColorPaletteStackEntry* entry = (ColorPaletteStackEntry*)malloc(sizeof(*entry)); colorPaletteStack[tos] = entry; memcpy(entry->mappedColors, mappedColor, sizeof(mappedColor)); memcpy(entry->cmap, cmap, sizeof(cmap)); memcpy(entry->colorTable, colorTable, sizeof(colorTable)); tos++; return true; } // NOTE: Unused. // // 0x4C88E0 bool colorPopColorPalette() { if (tos == 0) { errorStr = aColor_cColor_0; return false; } tos--; ColorPaletteStackEntry* entry = colorPaletteStack[tos]; memcpy(mappedColor, entry->mappedColors, sizeof(mappedColor)); memcpy(cmap, entry->cmap, sizeof(cmap)); memcpy(colorTable, entry->colorTable, sizeof(colorTable)); free(entry); colorPaletteStack[tos] = NULL; setIntensityTables(); for (int index = 0; index < 256; index++) { setMixTableColor(index); } rebuildColorBlendTables(); return true; } // 0x4C89CC bool initColors() { if (colorsInited) { return true; } colorsInited = true; colorGamma(1.0); if (!loadColorTable("color.pal")) { return false; } setSystemPalette(cmap); return true; } // 0x4C8A18 void colorsClose() { for (int index = 0; index < 256; index++) { freeColorBlendTable(index); } for (int index = 0; index < tos; index++) { free(colorPaletteStack[index]); } tos = 0; } // 0x4C8A64 unsigned char* getColorPalette() { return cmap; } ================================================ FILE: src/plib/color/color.h ================================================ #ifndef FALLOUT_PLIB_COLOR_COLOR_H_ #define FALLOUT_PLIB_COLOR_COLOR_H_ #include #include #include "memory_defs.h" #define COLOR_PALETTE_STACK_CAPACITY 16 typedef unsigned char Color; typedef long ColorRGB; typedef unsigned char ColorIndex; typedef const char*(ColorNameMangleFunc)(const char*); typedef void(fade_bk_func)(); typedef int(ColorOpenFunc)(const char* path, int mode); typedef int(ColorReadFunc)(int fd, void* buffer, size_t size); typedef int(ColorCloseFunc)(int fd); typedef struct ColorPaletteStackEntry { unsigned char mappedColors[256]; unsigned char cmap[768]; unsigned char colorTable[32768]; } ColorPaletteStackEntry; extern unsigned char cmap[768]; extern unsigned char mappedColor[256]; extern Color colorMixAddTable[256][256]; extern unsigned char intensityColorTable[256][256]; extern Color colorMixMulTable[256][256]; extern unsigned char colorTable[32768]; void colorInitIO(ColorOpenFunc* openProc, ColorReadFunc* readProc, ColorCloseFunc* closeProc); void colorSetNameMangler(ColorNameMangleFunc* c); Color colorMixAdd(Color a, Color b); Color colorMixMul(Color a, Color b); int calculateColor(int a1, int a2); Color RGB2Color(ColorRGB c); int Color2RGB(int a1); void fadeSystemPalette(unsigned char* oldPalette, unsigned char* newPalette, int steps); void colorSetFadeBkFunc(fade_bk_func* callback); void setBlackSystemPalette(); void setSystemPalette(unsigned char* palette); unsigned char* getSystemPalette(); void setSystemPaletteEntries(unsigned char* a1, int a2, int a3); void setSystemPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b); void getSystemPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b); bool loadColorTable(const char* path); char* colorError(); void setColorPalette(unsigned char* pal); void setColorPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b); void getColorPaletteEntry(int entry, unsigned char* r, unsigned char* g, unsigned char* b); unsigned char* getColorBlendTable(int ch); void freeColorBlendTable(int a1); void colorRegisterAlloc(MallocProc* mallocProc, ReallocProc* reallocProc, FreeProc* freeProc); void colorGamma(double value); double colorGetGamma(); int colorMappedColor(ColorIndex i); bool colorPushColorPalette(); bool colorPopColorPalette(); bool initColors(); void colorsClose(); unsigned char* getColorPalette(); #endif /* FALLOUT_PLIB_COLOR_COLOR_H_ */ ================================================ FILE: src/plib/db/db.c ================================================ #include "plib/db/db.h" #include #include #include #include "plib/xfile/xfile.h" static int db_list_compare(const void* p1, const void* p2); // Generic file progress report handler. // // 0x51DEEC static FileReadProgressHandler* read_callback = NULL; // Bytes read so far while tracking progress. // // Once this value reaches [read_threshold] the handler is called // and this value resets to zero. // // 0x51DEF0 static int read_counter = 0; // The number of bytes to read between calls to progress handler. // // 0x673040 static int read_threshold; // 0x673044 static FileList* db_file_lists; // Opens file database. // // Returns -1 if [filePath1] was specified, but could not be opened by the // underlying xbase implementation. Result of opening [filePath2] is ignored. // Returns 0 on success. // // NOTE: There are two unknown parameters passed via edx and ecx. The [a2] is // always 0 at the calling sites, and [a4] is always 1. Both parameters are not // used, so it's impossible to figure out their meaning. // // 0x4C5D30 int db_init(const char* filePath1, int a2, const char* filePath2, int a4) { if (filePath1 != NULL) { if (!xaddpath(filePath1)) { return -1; } } if (filePath2 != NULL) { xaddpath(filePath2); } return 0; } // 0x4C5D54 int db_select(int dbHandle) { return 0; } // NOTE: Uncollapsed 0x4C5D54. int db_current() { return 0; } // 0x4C5D58 int db_total() { return 0; } // 0x4C5D60 void db_exit() { xsetpath(NULL); } // TODO: sizePtr should be long*. // // 0x4C5D68 int db_dir_entry(const char* filePath, int* sizePtr) { assert(filePath); // "filename", "db.c", 108 assert(sizePtr); // "de", "db.c", 109 File* stream = xfopen(filePath, "rb"); if (stream == NULL) { return -1; } *sizePtr = xfilelength(stream); xfclose(stream); return 0; } // 0x4C5DD4 int db_read_to_buf(const char* filePath, void* ptr) { assert(filePath); // "filename", "db.c", 141 assert(ptr); // "buf", "db.c", 142 File* stream = xfopen(filePath, "rb"); if (stream == NULL) { return -1; } long size = xfilelength(stream); if (read_callback != NULL) { unsigned char* byteBuffer = (unsigned char*)ptr; long remainingSize = size; long chunkSize = read_threshold - read_counter; while (remainingSize >= chunkSize) { size_t bytesRead = xfread(byteBuffer, sizeof(*byteBuffer), chunkSize, stream); byteBuffer += bytesRead; remainingSize -= bytesRead; read_counter = 0; read_callback(); chunkSize = read_threshold; } if (remainingSize != 0) { read_counter += xfread(byteBuffer, sizeof(*byteBuffer), remainingSize, stream); } } else { xfread(ptr, 1, size, stream); } xfclose(stream); return 0; } // 0x4C5EB4 int db_fclose(File* stream) { return xfclose(stream); } // 0x4C5EC8 File* db_fopen(const char* filename, const char* mode) { return xfopen(filename, mode); } // 0x4C5ED0 int db_fprintf(File* stream, const char* format, ...) { assert(format); // "format", "db.c", 224 va_list args; va_start(args, format); int rc = xvfprintf(stream, format, args); va_end(args); return rc; } // 0x4C5F24 int db_fgetc(File* stream) { if (read_callback != NULL) { int ch = xfgetc(stream); read_counter++; if (read_counter >= read_threshold) { read_callback(); read_counter = 0; } return ch; } return xfgetc(stream); } // 0x4C5F70 char* db_fgets(char* string, size_t size, File* stream) { if (read_callback != NULL) { if (xfgets(string, size, stream) == NULL) { return NULL; } read_counter += strlen(string); while (read_counter >= read_threshold) { read_callback(); read_counter -= read_threshold; } return string; } return xfgets(string, size, stream); } // 0x4C5FEC int db_fputs(const char* string, File* stream) { return xfputs(string, stream); } // 0x4C5FFC size_t db_fread(void* ptr, size_t size, size_t count, File* stream) { if (read_callback != NULL) { unsigned char* byteBuffer = (unsigned char*)ptr; size_t totalBytesRead = 0; long remainingSize = size * count; long chunkSize = read_threshold - read_counter; while (remainingSize >= chunkSize) { size_t bytesRead = xfread(byteBuffer, sizeof(*byteBuffer), chunkSize, stream); byteBuffer += bytesRead; totalBytesRead += bytesRead; remainingSize -= bytesRead; read_counter = 0; read_callback(); chunkSize = read_threshold; } if (remainingSize != 0) { size_t bytesRead = xfread(byteBuffer, sizeof(*byteBuffer), remainingSize, stream); read_counter += bytesRead; totalBytesRead += bytesRead; } return totalBytesRead / size; } return xfread(ptr, size, count, stream); } // 0x4C60B8 size_t db_fwrite(const void* buf, size_t size, size_t count, File* stream) { return xfwrite(buf, size, count, stream); } // 0x4C60C0 int db_fseek(File* stream, long offset, int origin) { return xfseek(stream, offset, origin); } // 0x4C60C8 long db_ftell(File* stream) { return xftell(stream); } // 0x4C60D0 void db_rewind(File* stream) { xrewind(stream); } // 0x4C60D8 int db_feof(File* stream) { return xfeof(stream); } // NOTE: Not sure about signness. // // 0x4C60E0 int db_freadByte(File* stream, unsigned char* valuePtr) { int value = db_fgetc(stream); if (value == -1) { return -1; } *valuePtr = value & 0xFF; return 0; } // NOTE: Not sure about signness. // // 0x4C60F4 int db_freadShort(File* stream, unsigned short* valuePtr) { unsigned char high; // NOTE: Uninline. if (db_freadByte(stream, &high) == -1) { return -1; } unsigned char low; // NOTE: Uninline. if (db_freadByte(stream, &low) == -1) { return -1; } *valuePtr = (high << 8) | low; return 0; } // 0x4C614C int db_freadInt(File* stream, int* valuePtr) { int value; if (xfread(&value, 4, 1, stream) == -1) { return -1; } *valuePtr = ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000); return 0; } // NOTE: Uncollapsed 0x4C614C. int db_freadLong(File* stream, unsigned long* valuePtr) { return db_freadInt(stream, valuePtr); } // NOTE: Uncollapsed 0x4C614C. int db_freadFloat(File* stream, float* valuePtr) { return db_freadInt(stream, (unsigned long*)valuePtr); } int fileReadBool(File* stream, bool* valuePtr) { int value; if (db_freadInt(stream, &value) == -1) { return -1; } *valuePtr = (value != 0); return 0; } // NOTE: Not sure about signness. // // 0x4C61AC int db_fwriteByte(File* stream, unsigned char value) { return xfputc(value, stream); }; // 0x4C61C8 int db_fwriteShort(File* stream, unsigned short value) { // NOTE: Uninline. if (db_fwriteByte(stream, (value >> 8) & 0xFF) == -1) { return -1; } // NOTE: Uninline. if (db_fwriteByte(stream, value & 0xFF) == -1) { return -1; } return 0; } // 0x4C6214 int db_fwriteInt(File* stream, int value) { // NOTE: Uninline. return db_fwriteLong(stream, value); } // 0x4C6244 int db_fwriteLong(File* stream, unsigned long value) { if (db_fwriteShort(stream, (value >> 16) & 0xFFFF) == -1) { return -1; } if (db_fwriteShort(stream, value & 0xFFFF) == -1) { return -1; } return 0; } // 0x4C62C4 int db_fwriteFloat(File* stream, float value) { // NOTE: Uninline. return db_fwriteLong(stream, *(unsigned long*)&value); } int fileWriteBool(File* stream, bool value) { return db_fwriteLong(stream, value ? 1 : 0); } // 0x4C62FC int db_freadByteCount(File* stream, unsigned char* arr, int count) { for (int index = 0; index < count; index++) { unsigned char ch; // NOTE: Uninline. if (db_freadByte(stream, &ch) == -1) { return -1; } arr[index] = ch; } return 0; } // 0x4C6330 int db_freadShortCount(File* stream, unsigned short* arr, int count) { for (int index = 0; index < count; index++) { short value; // NOTE: Uninline. if (db_freadShort(stream, &value) == -1) { return -1; } arr[index] = value; } return 0; } // 0x4C63BC int db_freadIntCount(File* stream, int* arr, int count) { if (count == 0) { return 0; } if (db_fread(arr, sizeof(*arr) * count, 1, stream) < 1) { return -1; } for (int index = 0; index < count; index++) { int value = arr[index]; arr[index] = ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) | ((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000); } return 0; } // NOTE: Uncollapsed 0x4C63BC. int db_freadLongCount(File* stream, unsigned long* arr, int count) { return db_freadIntCount(stream, arr, count); } // 0x4C6464 int db_fwriteByteCount(File* stream, unsigned char* arr, int count) { for (int index = 0; index < count; index++) { // NOTE: Uninline. if (db_fwriteByte(stream, arr[index]) == -1) { return -1; } } return 0; } // 0x4C6490 int db_fwriteShortCount(File* stream, unsigned short* arr, int count) { for (int index = 0; index < count; index++) { // NOTE: Uninline. if (db_fwriteShort(stream, arr[index]) == -1) { return -1; } } return 0; } // 0x4C64F8 int db_fwriteIntCount(File* stream, int* arr, int count) { for (int index = 0; index < count; index++) { // NOTE: Uninline. if (db_fwriteLong(stream, arr[index]) == -1) { return -1; } } return 0; } // 0x4C6550 int db_fwriteLongCount(File* stream, unsigned long* arr, int count) { for (int index = 0; index < count; index++) { int value = arr[index]; // NOTE: Uninline. if (db_fwriteShort(stream, (value >> 16) & 0xFFFF) == -1) { return -1; } // NOTE: Uninline. if (db_fwriteShort(stream, value & 0xFFFF) == -1) { return -1; } } return 0; } // 0x4C6628 int db_get_file_list(const char* pattern, char*** fileNameListPtr, int a3, int a4) { FileList* fileList = (FileList*)malloc(sizeof(*fileList)); if (fileList == NULL) { return 0; } memset(fileList, 0, sizeof(*fileList)); XList* xlist = &(fileList->xlist); if (!xbuild_filelist(pattern, xlist)) { free(fileList); return 0; } int length = 0; if (xlist->fileNamesLength != 0) { qsort(xlist->fileNames, xlist->fileNamesLength, sizeof(*xlist->fileNames), db_list_compare); int fileNamesLength = xlist->fileNamesLength; for (int index = 0; index < fileNamesLength - 1; index++) { if (stricmp(xlist->fileNames[index], xlist->fileNames[index + 1]) == 0) { char* temp = xlist->fileNames[index + 1]; memmove(&(xlist->fileNames[index + 1]), &(xlist->fileNames[index + 2]), sizeof(*xlist->fileNames) * (xlist->fileNamesLength - index - 1)); xlist->fileNames[xlist->fileNamesLength - 1] = temp; fileNamesLength--; index--; } } bool isWildcard = *pattern == '*'; for (int index = 0; index < fileNamesLength; index += 1) { const char* name = xlist->fileNames[index]; char dir[_MAX_DIR]; char fileName[_MAX_FNAME]; char extension[_MAX_EXT]; _splitpath(name, NULL, dir, fileName, extension); if (!isWildcard || *dir == '\0' || strchr(dir, '\\') == NULL) { // FIXME: There is a buffer overlow bug in this implementation. // `fileNames` entries are dynamically allocated strings // themselves produced by `strdup` in `xlistenumfunc`. // In some circumstances we can end up placing long file name // in a short buffer (if that shorter buffer is alphabetically // preceding current file name). // // It can be easily spotted by creating `a.txt` in the game // directory and then trying print character data from character // editor. Because of compiler differencies original game will // crash immediately, and RE can crash anytime after closing // file dialog. sprintf(xlist->fileNames[length], "%s%s", fileName, extension); length++; } } } fileList->next = db_file_lists; db_file_lists = fileList; *fileNameListPtr = xlist->fileNames; return length; } // 0x4C6868 void db_free_file_list(char*** fileNameListPtr, int a2) { if (db_file_lists == NULL) { return; } FileList* currentFileList = db_file_lists; FileList* previousFileList = db_file_lists; while (*fileNameListPtr != currentFileList->xlist.fileNames) { previousFileList = currentFileList; currentFileList = currentFileList->next; if (currentFileList == NULL) { return; } } if (previousFileList == db_file_lists) { db_file_lists = currentFileList->next; } else { previousFileList->next = currentFileList->next; } xfree_filelist(&(currentFileList->xlist)); free(currentFileList); } // NOTE: This function does nothing. It was probably used to set memory procs // for building file name list. // // 0x4C68B8 void db_register_mem(MallocProc* mallocProc, StrdupProc* strdupProc, FreeProc* freeProc) { } // TODO: Return type should be long. // // 0x4C68BC int db_filelength(File* stream) { return xfilelength(stream); } // 0x4C68C4 void db_register_callback(FileReadProgressHandler* handler, int size) { if (handler != NULL && size != 0) { read_callback = handler; read_threshold = size; } else { read_callback = NULL; read_threshold = 0; } } // NOTE: This function is called when fallout2.cfg has "hashing" enabled, but // it does nothing. It's impossible to guess it's name. // // 0x4C68E4 void db_enable_hash_table() { } // 0x4C68E8 static int db_list_compare(const void* p1, const void* p2) { return stricmp(*(const char**)p1, *(const char**)p2); } ================================================ FILE: src/plib/db/db.h ================================================ #ifndef FALLOUT_PLIB_DB_DB_H_ #define FALLOUT_PLIB_DB_DB_H_ #include #include #include "memory_defs.h" #include "plib/xfile/xfile.h" typedef XFile File; typedef void FileReadProgressHandler(); typedef char* StrdupProc(const char* string); typedef struct FileList { XList xlist; struct FileList* next; } FileList; int db_init(const char* filePath1, int a2, const char* filePath2, int a4); int db_select(int dbHandle); int db_current(); int db_total(); void db_exit(); int db_dir_entry(const char* filePath, int* sizePtr); int db_read_to_buf(const char* filePath, void* ptr); int db_fclose(File* stream); File* db_fopen(const char* filename, const char* mode); int db_fprintf(File* stream, const char* format, ...); int db_fgetc(File* stream); char* db_fgets(char* str, size_t size, File* stream); int db_fputs(const char* s, File* stream); size_t db_fread(void* buf, size_t size, size_t count, File* stream); size_t db_fwrite(const void* buf, size_t size, size_t count, File* stream); int db_fseek(File* stream, long offset, int origin); long db_ftell(File* stream); void db_rewind(File* stream); int db_feof(File* stream); int db_freadByte(File* stream, unsigned char* valuePtr); int db_freadShort(File* stream, unsigned short* valuePtr); int db_freadInt(File* stream, int* valuePtr); int db_freadLong(File* stream, unsigned long* valuePtr); int db_freadFloat(File* stream, float* valuePtr); int fileReadBool(File* stream, bool* valuePtr); int db_fwriteByte(File* stream, unsigned char value); int db_fwriteShort(File* stream, unsigned short value); int db_fwriteInt(File* stream, int value); int db_fwriteLong(File* stream, unsigned long value); int db_fwriteFloat(File* stream, float value); int fileWriteBool(File* stream, bool value); int db_freadByteCount(File* stream, unsigned char* arr, int count); int db_freadShortCount(File* stream, unsigned short* arr, int count); int db_freadIntCount(File* stream, int* arr, int count); int db_freadLongCount(File* stream, unsigned long* arr, int count); int db_fwriteByteCount(File* stream, unsigned char* arr, int count); int db_fwriteShortCount(File* stream, unsigned short* arr, int count); int db_fwriteIntCount(File* stream, int* arr, int count); int db_fwriteLongCount(File* stream, unsigned long* arr, int count); int db_get_file_list(const char* pattern, char*** fileNames, int a3, int a4); void db_free_file_list(char*** fileNames, int a2); void db_register_mem(MallocProc* mallocProc, StrdupProc* strdupProc, FreeProc* freeProc); int db_filelength(File* stream); void db_register_callback(FileReadProgressHandler* handler, int size); void db_enable_hash_table(); #endif /* FALLOUT_PLIB_DB_DB_H_ */ ================================================ FILE: src/plib/gnw/button.c ================================================ #include "plib/gnw/button.h" #include "plib/gnw/input.h" #include "plib/gnw/grbuf.h" #include "plib/color/color.h" #include "plib/gnw/gnw.h" #include "plib/gnw/memory.h" #include "plib/gnw/mouse.h" #include "plib/gnw/text.h" // 0x51E404 static int last_button_winID = -1; // 0x6ADF40 static RadioGroup btn_grp[RADIO_GROUP_LIST_CAPACITY]; static Button* button_create(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, int flags, unsigned char* up, unsigned char* dn, unsigned char* hover); static bool button_under_mouse(Button* button, Rect* rect); static int button_check_group(Button* button); static void button_draw(Button* button, Window* window, unsigned char* data, int a4, Rect* a5, int a6); // 0x4D8260 int win_register_button(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, unsigned char* up, unsigned char* dn, unsigned char* hover, int flags) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (w == NULL) { return -1; } if (up == NULL && (dn != NULL || hover != NULL)) { return -1; } Button* button = button_create(win, x, y, width, height, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, flags | BUTTON_FLAG_0x010000, up, dn, hover); if (button == NULL) { return -1; } button_draw(button, w, button->mouseUpImage, 0, NULL, 0); return button->id; } // 0x4D8308 int win_register_text_button(int win, int x, int y, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, const char* title, int flags) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (w == NULL) { return -1; } int buttonWidth = text_width(title) + 16; int buttonHeight = text_height() + 7; unsigned char* normal = (unsigned char*)mem_malloc(buttonWidth * buttonHeight); if (normal == NULL) { return -1; } unsigned char* pressed = (unsigned char*)mem_malloc(buttonWidth * buttonHeight); if (pressed == NULL) { mem_free(normal); return -1; } if (w->field_20 == 256 && GNW_texture != NULL) { // TODO: Incomplete. } else { buf_fill(normal, buttonWidth, buttonHeight, buttonWidth, w->field_20); buf_fill(pressed, buttonWidth, buttonHeight, buttonWidth, w->field_20); } lighten_buf(normal, buttonWidth, buttonHeight, buttonWidth); text_to_buf(normal + buttonWidth * 3 + 8, title, buttonWidth, buttonWidth, colorTable[GNW_wcolor[3]]); draw_shaded_box(normal, buttonWidth, 2, 2, buttonWidth - 3, buttonHeight - 3, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]); draw_shaded_box(normal, buttonWidth, 1, 1, buttonWidth - 2, buttonHeight - 2, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]); draw_box(normal, buttonWidth, 0, 0, buttonWidth - 1, buttonHeight - 1, colorTable[0]); text_to_buf(pressed + buttonWidth * 4 + 9, title, buttonWidth, buttonWidth, colorTable[GNW_wcolor[3]]); draw_shaded_box(pressed, buttonWidth, 2, 2, buttonWidth - 3, buttonHeight - 3, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); draw_shaded_box(pressed, buttonWidth, 1, 1, buttonWidth - 2, buttonHeight - 2, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); draw_box(pressed, buttonWidth, 0, 0, buttonWidth - 1, buttonHeight - 1, colorTable[0]); Button* button = button_create(win, x, y, buttonWidth, buttonHeight, mouseEnterEventCode, mouseExitEventCode, mouseDownEventCode, mouseUpEventCode, flags, normal, pressed, NULL); if (button == NULL) { mem_free(normal); mem_free(pressed); return -1; } button_draw(button, w, button->mouseUpImage, 0, NULL, 0); return button->id; } // 0x4D8674 int win_register_button_disable(int btn, unsigned char* up, unsigned char* down, unsigned char* hover) { if (!GNW_win_init_flag) { return -1; } Button* button = GNW_find_button(btn, NULL); if (button == NULL) { return -1; } button->field_3C = up; button->field_40 = down; button->field_44 = hover; return 0; } // 0x4D86A8 int win_register_button_image(int btn, unsigned char* up, unsigned char* down, unsigned char* hover, int a5) { if (!GNW_win_init_flag) { return -1; } if (up == NULL && (down != NULL || hover != NULL)) { return -1; } Window* w; Button* button = GNW_find_button(btn, &w); if (button == NULL) { return -1; } if (!(button->flags & BUTTON_FLAG_0x010000)) { return -1; } unsigned char* data = button->currentImage; if (data == button->mouseUpImage) { button->currentImage = up; } else if (data == button->mouseDownImage) { button->currentImage = down; } else if (data == button->mouseHoverImage) { button->currentImage = hover; } button->mouseUpImage = up; button->mouseDownImage = down; button->mouseHoverImage = hover; button_draw(button, w, button->currentImage, a5, NULL, 0); return 0; } // Sets primitive callbacks on the button. // // 0x4D8758 int win_register_button_func(int btn, ButtonCallback* mouseEnterProc, ButtonCallback* mouseExitProc, ButtonCallback* mouseDownProc, ButtonCallback* mouseUpProc) { if (!GNW_win_init_flag) { return -1; } Button* button = GNW_find_button(btn, NULL); if (button == NULL) { return -1; } button->mouseEnterProc = mouseEnterProc; button->mouseExitProc = mouseExitProc; button->leftMouseDownProc = mouseDownProc; button->leftMouseUpProc = mouseUpProc; return 0; } // 0x4D8798 int win_register_right_button(int btn, int rightMouseDownEventCode, int rightMouseUpEventCode, ButtonCallback* rightMouseDownProc, ButtonCallback* rightMouseUpProc) { if (!GNW_win_init_flag) { return -1; } Button* button = GNW_find_button(btn, NULL); if (button == NULL) { return -1; } button->rightMouseDownEventCode = rightMouseDownEventCode; button->rightMouseUpEventCode = rightMouseUpEventCode; button->rightMouseDownProc = rightMouseDownProc; button->rightMouseUpProc = rightMouseUpProc; if (rightMouseDownEventCode != -1 || rightMouseUpEventCode != -1 || rightMouseDownProc != NULL || rightMouseUpProc != NULL) { button->flags |= BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED; } else { button->flags &= ~BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED; } return 0; } // Sets button state callbacks. // [a2] - when button is transitioning to pressed state // [a3] - when button is returned to unpressed state // // The changes in the state are tied to graphical state, therefore these callbacks are not generated for // buttons with no graphics. // // These callbacks can be triggered several times during tracking if mouse leaves button's rectangle without releasing mouse buttons. // // 0x4D87F8 int win_register_button_sound_func(int btn, ButtonCallback* onPressed, ButtonCallback* onUnpressed) { if (!GNW_win_init_flag) { return -1; } Button* button = GNW_find_button(btn, NULL); if (button == NULL) { return -1; } button->onPressed = onPressed; button->onUnpressed = onUnpressed; return 0; } // 0x4D8828 int win_register_button_mask(int btn, unsigned char* mask) { if (!GNW_win_init_flag) { return -1; } Button* button = GNW_find_button(btn, NULL); if (button == NULL) { return -1; } button->mask = mask; return 0; } // 0x4D8854 static Button* button_create(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, int flags, unsigned char* up, unsigned char* dn, unsigned char* hover) { Window* w = GNW_find(win); if (w == NULL) { return NULL; } Button* button = (Button*)mem_malloc(sizeof(*button)); if (button == NULL) { return NULL; } if ((flags & BUTTON_FLAG_0x01) == 0) { if ((flags & BUTTON_FLAG_0x02) != 0) { flags &= ~BUTTON_FLAG_0x02; } if ((flags & BUTTON_FLAG_0x04) != 0) { flags &= ~BUTTON_FLAG_0x04; } } // NOTE: Uninline. int buttonId = button_new_id(); button->id = buttonId; button->flags = flags; button->rect.ulx = x; button->rect.uly = y; button->rect.lrx = x + width - 1; button->rect.lry = y + height - 1; button->mouseEnterEventCode = mouseEnterEventCode; button->mouseExitEventCode = mouseExitEventCode; button->lefMouseDownEventCode = mouseDownEventCode; button->leftMouseUpEventCode = mouseUpEventCode; button->rightMouseDownEventCode = -1; button->rightMouseUpEventCode = -1; button->mouseUpImage = up; button->mouseDownImage = dn; button->mouseHoverImage = hover; button->field_3C = NULL; button->field_40 = NULL; button->field_44 = NULL; button->currentImage = NULL; button->mask = NULL; button->mouseEnterProc = NULL; button->mouseExitProc = NULL; button->leftMouseDownProc = NULL; button->leftMouseUpProc = NULL; button->rightMouseDownProc = NULL; button->rightMouseUpProc = NULL; button->onPressed = NULL; button->onUnpressed = NULL; button->radioGroup = NULL; button->prev = NULL; button->next = w->buttonListHead; if (button->next != NULL) { button->next->prev = button; } w->buttonListHead = button; return button; } // 0x4D89E4 bool win_button_down(int btn) { if (!GNW_win_init_flag) { return false; } Button* button = GNW_find_button(btn, NULL); if (button == NULL) { return false; } if ((button->flags & BUTTON_FLAG_0x01) != 0 && (button->flags & BUTTON_FLAG_0x020000) != 0) { return true; } return false; } // 0x4D8A10 int GNW_check_buttons(Window* w, int* keyCodePtr) { Rect v58; Button* field_34; Button* field_38; Button* button; if ((w->flags & WINDOW_HIDDEN) != 0) { return -1; } button = w->buttonListHead; field_34 = w->field_34; field_38 = w->field_38; if (field_34 != NULL) { rectCopy(&v58, &(field_34->rect)); rectOffset(&v58, w->rect.ulx, w->rect.uly); } else if (field_38 != NULL) { rectCopy(&v58, &(field_38->rect)); rectOffset(&v58, w->rect.ulx, w->rect.uly); } *keyCodePtr = -1; if (mouse_click_in(w->rect.ulx, w->rect.uly, w->rect.lrx, w->rect.lry)) { int mouseEvent = mouse_get_buttons(); if ((w->flags & WINDOW_FLAG_0x40) || (mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) == 0) { if (mouseEvent == 0) { w->field_38 = NULL; } } else { win_show(w->id); } if (field_34 != NULL) { if (!button_under_mouse(field_34, &v58)) { if (!(field_34->flags & BUTTON_FLAG_DISABLED)) { *keyCodePtr = field_34->mouseExitEventCode; } if ((field_34->flags & BUTTON_FLAG_0x01) && (field_34->flags & BUTTON_FLAG_0x020000)) { button_draw(field_34, w, field_34->mouseDownImage, 1, NULL, 1); } else { button_draw(field_34, w, field_34->mouseUpImage, 1, NULL, 1); } w->field_34 = NULL; last_button_winID = w->id; if (!(field_34->flags & BUTTON_FLAG_DISABLED)) { if (field_34->mouseExitProc != NULL) { field_34->mouseExitProc(field_34->id, *keyCodePtr); if (!(field_34->flags & BUTTON_FLAG_0x40)) { *keyCodePtr = -1; } } } return 0; } button = field_34; } else if (field_38 != NULL) { if (button_under_mouse(field_38, &v58)) { if (!(field_38->flags & BUTTON_FLAG_DISABLED)) { *keyCodePtr = field_38->mouseEnterEventCode; } if ((field_38->flags & BUTTON_FLAG_0x01) && (field_38->flags & BUTTON_FLAG_0x020000)) { button_draw(field_38, w, field_38->mouseDownImage, 1, NULL, 1); } else { button_draw(field_38, w, field_38->mouseUpImage, 1, NULL, 1); } w->field_34 = field_38; last_button_winID = w->id; if (!(field_38->flags & BUTTON_FLAG_DISABLED)) { if (field_38->mouseEnterProc != NULL) { field_38->mouseEnterProc(field_38->id, *keyCodePtr); if (!(field_38->flags & BUTTON_FLAG_0x40)) { *keyCodePtr = -1; } } } return 0; } } int v25 = last_button_winID; if (last_button_winID != -1 && last_button_winID != w->id) { Window* v26 = GNW_find(last_button_winID); if (v26 != NULL) { last_button_winID = -1; Button* v28 = v26->field_34; if (v28 != NULL) { if (!(v28->flags & BUTTON_FLAG_DISABLED)) { *keyCodePtr = v28->mouseExitEventCode; } if ((v28->flags & BUTTON_FLAG_0x01) && (v28->flags & BUTTON_FLAG_0x020000)) { button_draw(v28, v26, v28->mouseDownImage, 1, NULL, 1); } else { button_draw(v28, v26, v28->mouseUpImage, 1, NULL, 1); } v26->field_38 = NULL; v26->field_34 = NULL; if (!(v28->flags & BUTTON_FLAG_DISABLED)) { if (v28->mouseExitProc != NULL) { v28->mouseExitProc(v28->id, *keyCodePtr); if (!(v28->flags & BUTTON_FLAG_0x40)) { *keyCodePtr = -1; } } } return 0; } } } ButtonCallback* cb = NULL; while (button != NULL) { if (!(button->flags & BUTTON_FLAG_DISABLED)) { rectCopy(&v58, &(button->rect)); rectOffset(&v58, w->rect.ulx, w->rect.uly); if (button_under_mouse(button, &v58)) { if (!(button->flags & BUTTON_FLAG_DISABLED)) { if ((mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0) { if ((mouseEvent & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0 && (button->flags & BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED) == 0) { button = NULL; break; } if (button != w->field_34 && button != w->field_38) { break; } w->field_38 = button; w->field_34 = button; if ((button->flags & BUTTON_FLAG_0x01) != 0) { if ((button->flags & BUTTON_FLAG_0x02) != 0) { if ((button->flags & BUTTON_FLAG_0x020000) != 0) { if (!(button->flags & BUTTON_FLAG_0x04)) { if (button->radioGroup != NULL) { button->radioGroup->field_4--; } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { *keyCodePtr = button->leftMouseUpEventCode; cb = button->leftMouseUpProc; } else { *keyCodePtr = button->rightMouseUpEventCode; cb = button->rightMouseUpProc; } button->flags &= ~BUTTON_FLAG_0x020000; } } else { if (button_check_group(button) == -1) { button = NULL; break; } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { *keyCodePtr = button->lefMouseDownEventCode; cb = button->leftMouseDownProc; } else { *keyCodePtr = button->rightMouseDownEventCode; cb = button->rightMouseDownProc; } button->flags |= BUTTON_FLAG_0x020000; } } } else { if (button_check_group(button) == -1) { button = NULL; break; } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0) { *keyCodePtr = button->lefMouseDownEventCode; cb = button->leftMouseDownProc; } else { *keyCodePtr = button->rightMouseDownEventCode; cb = button->rightMouseDownProc; } } button_draw(button, w, button->mouseDownImage, 1, NULL, 1); break; } Button* v49 = w->field_38; if (button == v49 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_UP) != 0) { w->field_38 = NULL; w->field_34 = v49; if (v49->flags & BUTTON_FLAG_0x01) { if (!(v49->flags & BUTTON_FLAG_0x02)) { if (v49->flags & BUTTON_FLAG_0x020000) { if (!(v49->flags & BUTTON_FLAG_0x04)) { if (v49->radioGroup != NULL) { v49->radioGroup->field_4--; } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { *keyCodePtr = button->leftMouseUpEventCode; cb = button->leftMouseUpProc; } else { *keyCodePtr = button->rightMouseUpEventCode; cb = button->rightMouseUpProc; } button->flags &= ~BUTTON_FLAG_0x020000; } } else { if (button_check_group(v49) == -1) { button = NULL; button_draw(v49, w, v49->mouseUpImage, 1, NULL, 1); break; } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { *keyCodePtr = v49->lefMouseDownEventCode; cb = v49->leftMouseDownProc; } else { *keyCodePtr = v49->rightMouseDownEventCode; cb = v49->rightMouseDownProc; } v49->flags |= BUTTON_FLAG_0x020000; } } } else { if (v49->flags & BUTTON_FLAG_0x020000) { if (v49->radioGroup != NULL) { v49->radioGroup->field_4--; } } if ((mouseEvent & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) { *keyCodePtr = v49->leftMouseUpEventCode; cb = v49->leftMouseUpProc; } else { *keyCodePtr = v49->rightMouseUpEventCode; cb = v49->rightMouseUpProc; } } if (button->mouseHoverImage != NULL) { button_draw(button, w, button->mouseHoverImage, 1, NULL, 1); } else { button_draw(button, w, button->mouseUpImage, 1, NULL, 1); } break; } } if (w->field_34 == NULL && mouseEvent == 0) { w->field_34 = button; if (!(button->flags & BUTTON_FLAG_DISABLED)) { *keyCodePtr = button->mouseEnterEventCode; cb = button->mouseEnterProc; } button_draw(button, w, button->mouseHoverImage, 1, NULL, 1); } break; } } button = button->next; } if (button != NULL) { if ((button->flags & BUTTON_FLAG_0x10) != 0 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_REPEAT) == 0) { win_drag(w->id); button_draw(button, w, button->mouseUpImage, 1, NULL, 1); } } else if ((w->flags & WINDOW_FLAG_0x80) != 0) { v25 |= mouseEvent << 8; if ((mouseEvent & MOUSE_EVENT_ANY_BUTTON_DOWN) != 0 && (mouseEvent & MOUSE_EVENT_ANY_BUTTON_REPEAT) == 0) { win_drag(w->id); } } last_button_winID = w->id; if (button != NULL) { if (cb != NULL) { cb(button->id, *keyCodePtr); if (!(button->flags & BUTTON_FLAG_0x40)) { *keyCodePtr = -1; } } } return 0; } if (field_34 != NULL) { *keyCodePtr = field_34->mouseExitEventCode; unsigned char* data; if ((field_34->flags & BUTTON_FLAG_0x01) && (field_34->flags & BUTTON_FLAG_0x020000)) { data = field_34->mouseDownImage; } else { data = field_34->mouseUpImage; } button_draw(field_34, w, data, 1, NULL, 1); w->field_34 = NULL; } if (*keyCodePtr != -1) { last_button_winID = w->id; if ((field_34->flags & BUTTON_FLAG_DISABLED) == 0) { if (field_34->mouseExitProc != NULL) { field_34->mouseExitProc(field_34->id, *keyCodePtr); if (!(field_34->flags & BUTTON_FLAG_0x40)) { *keyCodePtr = -1; } } } return 0; } if (field_34 != NULL) { if ((field_34->flags & BUTTON_FLAG_DISABLED) == 0) { if (field_34->mouseExitProc != NULL) { field_34->mouseExitProc(field_34->id, *keyCodePtr); } } } return -1; } // 0x4D9214 static bool button_under_mouse(Button* button, Rect* rect) { if (!mouse_click_in(rect->ulx, rect->uly, rect->lrx, rect->lry)) { return false; } if (button->mask == NULL) { return true; } int x; int y; mouse_get_position(&x, &y); x -= rect->ulx; y -= rect->uly; int width = button->rect.lrx - button->rect.ulx + 1; return button->mask[width * y + x] != 0; } // 0x4D927C int win_button_winID(int btn) { if (!GNW_win_init_flag) { return -1; } Window* w; if (GNW_find_button(btn, &w) == NULL) { return -1; } return w->id; } // 0x4D92B4 int win_last_button_winID() { return last_button_winID; } // 0x4D92BC int win_delete_button(int btn) { if (!GNW_win_init_flag) { return -1; } Window* w; Button* button = GNW_find_button(btn, &w); if (button == NULL) { return -1; } if (button->prev != NULL) { button->prev->next = button->next; } else { w->buttonListHead = button->next; } if (button->next != NULL) { button->next->prev = button->prev; } win_fill(w->id, button->rect.ulx, button->rect.uly, button->rect.lrx - button->rect.ulx + 1, button->rect.lry - button->rect.uly + 1, w->field_20); if (button == w->field_34) { w->field_34 = NULL; } if (button == w->field_38) { w->field_38 = NULL; } GNW_delete_button(button); return 0; } // 0x4D9374 void GNW_delete_button(Button* button) { if ((button->flags & BUTTON_FLAG_0x010000) == 0) { if (button->mouseUpImage != NULL) { mem_free(button->mouseUpImage); } if (button->mouseDownImage != NULL) { mem_free(button->mouseDownImage); } if (button->mouseHoverImage != NULL) { mem_free(button->mouseHoverImage); } if (button->field_3C != NULL) { mem_free(button->field_3C); } if (button->field_40 != NULL) { mem_free(button->field_40); } if (button->field_44 != NULL) { mem_free(button->field_44); } } RadioGroup* radioGroup = button->radioGroup; if (radioGroup != NULL) { for (int index = 0; index < radioGroup->buttonsLength; index++) { if (button == radioGroup->buttons[index]) { for (; index < radioGroup->buttonsLength - 1; index++) { radioGroup->buttons[index] = radioGroup->buttons[index + 1]; } radioGroup->buttonsLength--; break; } } } mem_free(button); } // NOTE: Unused. // // 0x4D9430 void win_delete_button_win(int btn, int inputEvent) { Button* button; Window* w; button = GNW_find_button(btn, &w); if (button != NULL) { win_delete(w->id); GNW_add_input_buffer(inputEvent); } } // NOTE: Inlined. // // 0x4D9458 int button_new_id() { int btn; btn = 1; while (GNW_find_button(btn, NULL) != NULL) { btn++; } return btn; } // 0x4D9474 int win_enable_button(int btn) { if (!GNW_win_init_flag) { return -1; } Window* w; Button* button = GNW_find_button(btn, &w); if (button == NULL) { return -1; } if ((button->flags & BUTTON_FLAG_DISABLED) != 0) { button->flags &= ~BUTTON_FLAG_DISABLED; button_draw(button, w, button->currentImage, 1, NULL, 0); } return 0; } // 0x4D94D0 int win_disable_button(int btn) { if (!GNW_win_init_flag) { return -1; } Window* w; Button* button = GNW_find_button(btn, &w); if (button == NULL) { return -1; } if ((button->flags & BUTTON_FLAG_DISABLED) == 0) { button->flags |= BUTTON_FLAG_DISABLED; button_draw(button, w, button->currentImage, 1, NULL, 0); if (button == w->field_34) { if (w->field_34->mouseExitEventCode != -1) { GNW_add_input_buffer(w->field_34->mouseExitEventCode); w->field_34 = NULL; } } } return 0; } // 0x4D9554 int win_set_button_rest_state(int btn, bool a2, int a3) { if (!GNW_win_init_flag) { return -1; } Window* w; Button* button = GNW_find_button(btn, &w); if (button == NULL) { return -1; } if ((button->flags & BUTTON_FLAG_0x01) != 0) { int keyCode = -1; if ((button->flags & BUTTON_FLAG_0x020000) != 0) { if (!a2) { button->flags &= ~BUTTON_FLAG_0x020000; if ((a3 & 0x02) == 0) { button_draw(button, w, button->mouseUpImage, 1, NULL, 0); } if (button->radioGroup != NULL) { button->radioGroup->field_4--; } keyCode = button->leftMouseUpEventCode; } } else { if (a2) { button->flags |= BUTTON_FLAG_0x020000; if ((a3 & 0x02) == 0) { button_draw(button, w, button->mouseDownImage, 1, NULL, 0); } if (button->radioGroup != NULL) { button->radioGroup->field_4++; } keyCode = button->lefMouseDownEventCode; } } if (keyCode != -1) { if ((a3 & 0x01) != 0) { GNW_add_input_buffer(keyCode); } } } return 0; } // 0x4D962C int win_group_check_buttons(int buttonCount, int* btns, int a3, void (*a4)(int)) { if (!GNW_win_init_flag) { return -1; } if (buttonCount >= RADIO_GROUP_BUTTON_LIST_CAPACITY) { return -1; } for (int groupIndex = 0; groupIndex < RADIO_GROUP_LIST_CAPACITY; groupIndex++) { RadioGroup* radioGroup = &(btn_grp[groupIndex]); if (radioGroup->buttonsLength == 0) { radioGroup->field_4 = 0; for (int buttonIndex = 0; buttonIndex < buttonCount; buttonIndex++) { Button* button = GNW_find_button(btns[buttonIndex], NULL); if (button == NULL) { return -1; } radioGroup->buttons[buttonIndex] = button; button->radioGroup = radioGroup; if ((button->flags & BUTTON_FLAG_0x020000) != 0) { radioGroup->field_4++; } } radioGroup->buttonsLength = buttonCount; radioGroup->field_0 = a3; radioGroup->field_8 = a4; return 0; } } return -1; } // 0x4D96EC int win_group_radio_buttons(int count, int* btns) { if (!GNW_win_init_flag) { return -1; } if (win_group_check_buttons(count, btns, 1, NULL) == -1) { return -1; } Button* button = GNW_find_button(btns[0], NULL); RadioGroup* radioGroup = button->radioGroup; for (int index = 0; index < radioGroup->buttonsLength; index++) { Button* v1 = radioGroup->buttons[index]; v1->flags |= BUTTON_FLAG_0x040000; } return 0; } // 0x4D9744 static int button_check_group(Button* button) { if (button->radioGroup == NULL) { return 0; } if ((button->flags & BUTTON_FLAG_0x040000) != 0) { if (button->radioGroup->field_4 > 0) { for (int index = 0; index < button->radioGroup->buttonsLength; index++) { Button* v1 = button->radioGroup->buttons[index]; if ((v1->flags & BUTTON_FLAG_0x020000) != 0) { v1->flags &= ~BUTTON_FLAG_0x020000; Window* w; GNW_find_button(v1->id, &w); button_draw(v1, w, v1->mouseUpImage, 1, NULL, 1); if (v1->leftMouseUpProc != NULL) { v1->leftMouseUpProc(v1->id, v1->leftMouseUpEventCode); } } } } if ((button->flags & BUTTON_FLAG_0x020000) == 0) { button->radioGroup->field_4++; } return 0; } if (button->radioGroup->field_4 < button->radioGroup->field_0) { if ((button->flags & BUTTON_FLAG_0x020000) == 0) { button->radioGroup->field_4++; } return 0; } if (button->radioGroup->field_8 != NULL) { button->radioGroup->field_8(button->id); } return -1; } // 0x4D9808 static void button_draw(Button* button, Window* w, unsigned char* data, int a4, Rect* a5, int a6) { unsigned char* previousImage = NULL; if (data != NULL) { Rect v2; rectCopy(&v2, &(button->rect)); rectOffset(&v2, w->rect.ulx, w->rect.uly); Rect v3; if (a5 != NULL) { if (rect_inside_bound(&v2, a5, &v2) == -1) { return; } rectCopy(&v3, &v2); rectOffset(&v3, -w->rect.ulx, -w->rect.uly); } else { rectCopy(&v3, &(button->rect)); } if (data == button->mouseUpImage && (button->flags & BUTTON_FLAG_0x020000)) { data = button->mouseDownImage; } if (button->flags & BUTTON_FLAG_DISABLED) { if (data == button->mouseUpImage) { data = button->field_3C; } else if (data == button->mouseDownImage) { data = button->field_40; } else if (data == button->mouseHoverImage) { data = button->field_44; } } else { if (data == button->field_3C) { data = button->mouseUpImage; } else if (data == button->field_40) { data = button->mouseDownImage; } else if (data == button->field_44) { data = button->mouseHoverImage; } } if (data) { if (a4 == 0) { int width = button->rect.lrx - button->rect.ulx + 1; if ((button->flags & BUTTON_FLAG_TRANSPARENT) != 0) { trans_buf_to_buf( data + (v3.uly - button->rect.uly) * width + v3.ulx - button->rect.ulx, v3.lrx - v3.ulx + 1, v3.lry - v3.uly + 1, width, w->buffer + w->width * v3.uly + v3.ulx, w->width); } else { buf_to_buf( data + (v3.uly - button->rect.uly) * width + v3.ulx - button->rect.ulx, v3.lrx - v3.ulx + 1, v3.lry - v3.uly + 1, width, w->buffer + w->width * v3.uly + v3.ulx, w->width); } } previousImage = button->currentImage; button->currentImage = data; if (a4 != 0) { GNW_win_refresh(w, &v2, 0); } } } if (a6) { if (previousImage != data) { if (data == button->mouseDownImage && button->onPressed != NULL) { button->onPressed(button->id, button->lefMouseDownEventCode); } else if (data == button->mouseUpImage && button->onUnpressed != NULL) { button->onUnpressed(button->id, button->leftMouseUpEventCode); } } } } // 0x4D9A58 void GNW_button_refresh(Window* w, Rect* rect) { Button* button = w->buttonListHead; if (button != NULL) { while (button->next != NULL) { button = button->next; } } while (button != NULL) { button_draw(button, w, button->currentImage, 0, rect, 0); button = button->prev; } } // 0x4D9AA0 int win_button_press_and_release(int btn) { if (!GNW_win_init_flag) { return -1; } Window* w; Button* button = GNW_find_button(btn, &w); if (button == NULL) { return -1; } button_draw(button, w, button->mouseDownImage, 1, NULL, 1); if (button->leftMouseDownProc != NULL) { button->leftMouseDownProc(btn, button->lefMouseDownEventCode); if ((button->flags & BUTTON_FLAG_0x40) != 0) { GNW_add_input_buffer(button->lefMouseDownEventCode); } } else { if (button->lefMouseDownEventCode != -1) { GNW_add_input_buffer(button->lefMouseDownEventCode); } } button_draw(button, w, button->mouseUpImage, 1, NULL, 1); if (button->leftMouseUpProc != NULL) { button->leftMouseUpProc(btn, button->leftMouseUpEventCode); if ((button->flags & BUTTON_FLAG_0x40) != 0) { GNW_add_input_buffer(button->leftMouseUpEventCode); } } else { if (button->leftMouseUpEventCode != -1) { GNW_add_input_buffer(button->leftMouseUpEventCode); } } return 0; } ================================================ FILE: src/plib/gnw/button.h ================================================ #ifndef FALLOUT_PLIB_GNW_BUTTON_H_ #define FALLOUT_PLIB_GNW_BUTTON_H_ #include #include "plib/gnw/gnw_types.h" int win_register_button(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, unsigned char* up, unsigned char* dn, unsigned char* hover, int flags); int win_register_text_button(int win, int x, int y, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, const char* title, int flags); int win_register_button_disable(int btn, unsigned char* up, unsigned char* down, unsigned char* hover); int win_register_button_image(int btn, unsigned char* up, unsigned char* down, unsigned char* hover, int a5); int win_register_button_func(int btn, ButtonCallback* mouseEnterProc, ButtonCallback* mouseExitProc, ButtonCallback* mouseDownProc, ButtonCallback* mouseUpProc); int win_register_right_button(int btn, int rightMouseDownEventCode, int rightMouseUpEventCode, ButtonCallback* rightMouseDownProc, ButtonCallback* rightMouseUpProc); int win_register_button_sound_func(int btn, ButtonCallback* onPressed, ButtonCallback* onUnpressed); int win_register_button_mask(int btn, unsigned char* mask); bool win_button_down(int btn); int GNW_check_buttons(Window* window, int* out_a2); int win_button_winID(int btn); int win_last_button_winID(); int win_delete_button(int btn); void GNW_delete_button(Button* ptr); void win_delete_button_win(int btn, int inputEvent); int button_new_id(); int win_enable_button(int btn); int win_disable_button(int btn); int win_set_button_rest_state(int btn, bool a2, int a3); int win_group_check_buttons(int a1, int* a2, int a3, void (*a4)(int)); int win_group_radio_buttons(int a1, int* a2); void GNW_button_refresh(Window* window, Rect* rect); int win_button_press_and_release(int btn); #endif /* FALLOUT_PLIB_GNW_BUTTON_H_ */ ================================================ FILE: src/plib/gnw/debug.c ================================================ #include "plib/gnw/debug.h" #include #include #include #include #define WIN32_LEAN_AND_MEAN #include #include "plib/gnw/memory.h" #include "plib/gnw/intrface.h" static int debug_mono(char* string); static int debug_log(char* string); static int debug_screen(char* string); static void debug_putc(int ch); static void debug_scroll(); static void debug_exit(void); // 0x51DEF8 static FILE* fd = NULL; // 0x51DEFC static int curx = 0; // 0x51DF00 static int cury = 0; // 0x51DF04 static DebugPrintProc* debug_func = NULL; // 0x4C6CD0 void GNW_debug_init() { atexit(debug_exit); } // 0x4C6CDC void debug_register_mono() { if (debug_func != debug_mono) { if (fd != NULL) { fclose(fd); fd = NULL; } debug_func = debug_mono; debug_clear(); } } // 0x4C6D18 void debug_register_log(const char* fileName, const char* mode) { if ((mode[0] == 'w' && mode[1] == 'a') && mode[1] == 't') { if (fd != NULL) { fclose(fd); } fd = fopen(fileName, mode); debug_func = debug_log; } } // 0x4C6D5C void debug_register_screen() { if (debug_func != debug_screen) { if (fd != NULL) { fclose(fd); fd = NULL; } debug_func = debug_screen; } } // 0x4C6D90 void debug_register_env() { const char* type = getenv("DEBUGACTIVE"); if (type == NULL) { return; } char* copy = (char*)mem_malloc(strlen(type) + 1); if (copy == NULL) { return; } strcpy(copy, type); strlwr(copy); if (strcmp(copy, "mono") == 0) { // NOTE: Uninline. debug_register_mono(); } else if (strcmp(copy, "log") == 0) { debug_register_log("debug.log", "wt"); } else if (strcmp(copy, "screen") == 0) { // NOTE: Uninline. debug_register_screen(); } else if (strcmp(copy, "gnw") == 0) { if (debug_func != win_debug) { if (fd != NULL) { fclose(fd); fd = NULL; } debug_func = win_debug; } } mem_free(copy); } // 0x4C6F18 void debug_register_func(DebugPrintProc* proc) { if (debug_func != proc) { if (fd != NULL) { fclose(fd); fd = NULL; } debug_func = proc; } } // 0x4C6F48 int debug_printf(const char* format, ...) { va_list args; va_start(args, format); int rc; if (debug_func != NULL) { char string[260]; vsprintf(string, format, args); rc = debug_func(string); } else { #ifdef _DEBUG char string[260]; vsprintf(string, format, args); OutputDebugStringA(string); #endif rc = -1; } va_end(args); return rc; } // 0x4C6F94 int debug_puts(char* string) { if (debug_func != NULL) { return debug_func(string); } return -1; } // 0x4C6FAC void debug_clear() { char* buffer; int x; int y; buffer = NULL; if (debug_func == debug_mono) { buffer = (char*)0xB0000; } else if (debug_func == debug_screen) { buffer = (char*)0xB8000; } if (buffer != NULL) { for (y = 0; y < 25; y++) { for (x = 0; x < 80; x++) { *buffer++ = ' '; *buffer++ = 7; } } cury = 0; curx = 0; } } // 0x4C7004 static int debug_mono(char* string) { if (debug_func == debug_mono) { while (*string != '\0') { char ch = *string++; debug_putc(ch); } } return 0; } // 0x4C7028 static int debug_log(char* string) { if (debug_func == debug_log) { if (fd == NULL) { return -1; } if (fprintf(fd, string) < 0) { return -1; } if (fflush(fd) == EOF) { return -1; } } return 0; } // 0x4C7068 static int debug_screen(char* string) { if (debug_func == debug_screen) { printf(string); } return 0; } // 0x4C709C static void debug_putc(int ch) { char* buffer; buffer = (char*)0xB0000; switch (ch) { case 7: printf("\x07"); return; case 8: if (curx > 0) { curx--; buffer += 2 * curx + 2 * 80 * cury; *buffer++ = ' '; *buffer = 7; } return; case 9: do { debug_putc(' '); } while ((curx - 1) % 4 != 0); return; case 13: curx = 0; return; default: buffer += 2 * curx + 2 * 80 * cury; *buffer++ = ch; *buffer = 7; curx++; if (curx < 80) { return; } // FALLTHROUGH case 10: curx = 0; cury++; if (cury > 24) { cury = 24; debug_scroll(); } return; } } // 0x4C71AC static void debug_scroll() { char* buffer; int x; int y; buffer = (char*)0xB0000; for (y = 0; y < 24; y++) { for (x = 0; x < 80 * 2; x++) { buffer[0] = buffer[80 * 2]; buffer++; } } for (x = 0; x < 80; x++) { *buffer++ = ' '; *buffer++ = 7; } } // 0x4C71E8 static void debug_exit(void) { if (fd != NULL) { fclose(fd); } } ================================================ FILE: src/plib/gnw/debug.h ================================================ #ifndef FALLOUT_PLIB_GNW_DEBUG_H_ #define FALLOUT_PLIB_GNW_DEBUG_H_ typedef int(DebugPrintProc)(char* string); void GNW_debug_init(); void debug_register_mono(); void debug_register_log(const char* fileName, const char* mode); void debug_register_screen(); void debug_register_env(); void debug_register_func(DebugPrintProc* proc); int debug_printf(const char* format, ...); int debug_puts(char* string); void debug_clear(); #endif /* FALLOUT_PLIB_GNW_DEBUG_H_ */ ================================================ FILE: src/plib/gnw/doscmdln.c ================================================ #include "plib/gnw/doscmdln.h" #include #include #define WIN32_LEAN_AND_MEAN #include // 0x4E3B90 void DOSCmdLineInit(DOSCmdLine* d) { if (d != NULL) { d->numArgs = 0; d->args = NULL; } } // 0x4E3BA4 bool DOSCmdLineCreate(DOSCmdLine* d, char* commandLine) { const char* delim = " \t"; int argc = 0; // Get the number of arguments in command line. if (*commandLine != '\0') { char* copy = strdup(commandLine); if (copy == NULL) { DOSCmdLineDestroy(d); return false; } char* tok = strtok(copy, delim); while (tok != NULL) { argc++; tok = strtok(NULL, delim); } free(copy); } // Make a room for argv[0] - program name. argc++; d->numArgs = argc; d->args = (char**)malloc(sizeof(*d->args) * argc); if (d->args == NULL) { // NOTE: Uninline. return DOSCmdLineFatalError(d); } for (int arg = 0; arg < argc; arg++) { d->args[arg] = NULL; } // Copy program name into argv[0]. char moduleFileName[MAX_PATH]; int moduleFileNameLength = GetModuleFileNameA(NULL, moduleFileName, MAX_PATH); if (moduleFileNameLength == 0) { // NOTE: Uninline. return DOSCmdLineFatalError(d); } if (moduleFileNameLength >= MAX_PATH) { moduleFileNameLength = MAX_PATH - 1; } moduleFileName[moduleFileNameLength] = '\0'; d->args[0] = strdup(moduleFileName); if (d->args[0] == NULL) { // NOTE: Uninline. return DOSCmdLineFatalError(d); } // Copy arguments from command line into argv. if (*commandLine != '\0') { char* copy = strdup(commandLine); if (copy == NULL) { DOSCmdLineDestroy(d); return false; } int arg = 1; char* tok = strtok(copy, delim); while (tok != NULL) { d->args[arg] = strdup(tok); tok = strtok(NULL, delim); arg++; } free(copy); } return true; } // 0x4E3D3C void DOSCmdLineDestroy(DOSCmdLine* d) { if (d->args != NULL) { // NOTE: Compiled code is slightly different - it decrements argc. for (int index = 0; index < d->numArgs; index++) { if (d->args[index] != NULL) { free(d->args[index]); } } free(d->args); } d->numArgs = 0; d->args = NULL; } // NOTE: Inlined. // // 0x4E3D88 bool DOSCmdLineFatalError(DOSCmdLine* d) { DOSCmdLineDestroy(d); return false; } ================================================ FILE: src/plib/gnw/doscmdln.h ================================================ #ifndef FALLOUT_PLIB_GNW_DOSCMDLN_H_ #define FALLOUT_PLIB_GNW_DOSCMDLN_H_ #include typedef struct tagDOSCmdLine { int numArgs; char** args; } DOSCmdLine; void DOSCmdLineInit(DOSCmdLine* d); bool DOSCmdLineCreate(DOSCmdLine* d, char* windowsCmdLine); void DOSCmdLineDestroy(DOSCmdLine* d); bool DOSCmdLineFatalError(DOSCmdLine* d); #endif /* FALLOUT_PLIB_GNW_DOSCMDLN_H_ */ ================================================ FILE: src/plib/gnw/dxinput.c ================================================ #include #include "plib/gnw/dxinput.h" #include "plib/gnw/gnw95dx.h" #include "plib/gnw/winmain.h" #define KEYBOARD_DEVICE_DATA_CAPACITY 32 // NOTE: There is no such define in DirectX SDK. I've taken it from Wine at // https://github.com/wine-mirror/wine/blob/master/dlls/dinput/data_formats.c. #define DIDFT_OPTIONAL 0x80000000 // NOTE: This define is different in the newer DirectX. Check DirectX SDK 3.0 // at https://github.com/masonmc/dxsdk3/blob/master/sdk/inc/dinput.h. #undef DIDFT_ANYINSTANCE #define DIDFT_ANYINSTANCE 0x0000FF00 static bool dxinput_mouse_init(); static void dxinput_mouse_exit(); static bool dxinput_keyboard_init(); static void dxinput_keyboard_exit(); // 0x4FCE90 static const DIOBJECTDATAFORMAT dfDIMouse[] = { { &GUID_XAxis, DIMOFS_X, DIDFT_ANYINSTANCE | DIDFT_AXIS, 0 }, { &GUID_YAxis, DIMOFS_Y, DIDFT_ANYINSTANCE | DIDFT_AXIS, 0 }, { &GUID_ZAxis, DIMOFS_Z, DIDFT_OPTIONAL | DIDFT_ANYINSTANCE | DIDFT_AXIS, 0 }, { NULL, DIMOFS_BUTTON0, DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 }, { NULL, DIMOFS_BUTTON1, DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 }, { NULL, DIMOFS_BUTTON2, DIDFT_OPTIONAL | DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 }, { NULL, DIMOFS_BUTTON3, DIDFT_OPTIONAL | DIDFT_ANYINSTANCE | DIDFT_BUTTON, 0 }, }; // 0x4FCF00 static const DIDATAFORMAT c_dfDIMouse = { sizeof(DIDATAFORMAT), sizeof(DIOBJECTDATAFORMAT), DIDF_RELAXIS, sizeof(DIMOUSESTATE), sizeof(dfDIMouse) / sizeof(dfDIMouse[0]), (LPDIOBJECTDATAFORMAT)dfDIMouse }; // 0x4FCF20 static const DIOBJECTDATAFORMAT dfDIKeyboard[] = { { &GUID_Key, 0, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(0), 0 }, { &GUID_Key, 1, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(1), 0 }, { &GUID_Key, 2, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(2), 0 }, { &GUID_Key, 3, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(3), 0 }, { &GUID_Key, 4, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(4), 0 }, { &GUID_Key, 5, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(5), 0 }, { &GUID_Key, 6, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(6), 0 }, { &GUID_Key, 7, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(7), 0 }, { &GUID_Key, 8, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(8), 0 }, { &GUID_Key, 9, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(9), 0 }, { &GUID_Key, 10, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(10), 0 }, { &GUID_Key, 11, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(11), 0 }, { &GUID_Key, 12, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(12), 0 }, { &GUID_Key, 13, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(13), 0 }, { &GUID_Key, 14, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(14), 0 }, { &GUID_Key, 15, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(15), 0 }, { &GUID_Key, 16, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(16), 0 }, { &GUID_Key, 17, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(17), 0 }, { &GUID_Key, 18, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(18), 0 }, { &GUID_Key, 19, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(19), 0 }, { &GUID_Key, 20, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(20), 0 }, { &GUID_Key, 21, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(21), 0 }, { &GUID_Key, 22, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(22), 0 }, { &GUID_Key, 23, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(23), 0 }, { &GUID_Key, 24, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(24), 0 }, { &GUID_Key, 25, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(25), 0 }, { &GUID_Key, 26, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(26), 0 }, { &GUID_Key, 27, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(27), 0 }, { &GUID_Key, 28, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(28), 0 }, { &GUID_Key, 29, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(29), 0 }, { &GUID_Key, 30, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(30), 0 }, { &GUID_Key, 31, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(31), 0 }, { &GUID_Key, 32, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(32), 0 }, { &GUID_Key, 33, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(33), 0 }, { &GUID_Key, 34, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(34), 0 }, { &GUID_Key, 35, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(35), 0 }, { &GUID_Key, 36, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(36), 0 }, { &GUID_Key, 37, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(37), 0 }, { &GUID_Key, 38, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(38), 0 }, { &GUID_Key, 39, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(39), 0 }, { &GUID_Key, 40, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(40), 0 }, { &GUID_Key, 41, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(41), 0 }, { &GUID_Key, 42, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(42), 0 }, { &GUID_Key, 43, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(43), 0 }, { &GUID_Key, 44, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(44), 0 }, { &GUID_Key, 45, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(45), 0 }, { &GUID_Key, 46, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(46), 0 }, { &GUID_Key, 47, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(47), 0 }, { &GUID_Key, 48, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(48), 0 }, { &GUID_Key, 49, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(49), 0 }, { &GUID_Key, 50, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(50), 0 }, { &GUID_Key, 51, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(51), 0 }, { &GUID_Key, 52, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(52), 0 }, { &GUID_Key, 53, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(53), 0 }, { &GUID_Key, 54, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(54), 0 }, { &GUID_Key, 55, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(55), 0 }, { &GUID_Key, 56, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(56), 0 }, { &GUID_Key, 57, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(57), 0 }, { &GUID_Key, 58, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(58), 0 }, { &GUID_Key, 59, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(59), 0 }, { &GUID_Key, 60, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(60), 0 }, { &GUID_Key, 61, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(61), 0 }, { &GUID_Key, 62, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(62), 0 }, { &GUID_Key, 63, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(63), 0 }, { &GUID_Key, 64, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(64), 0 }, { &GUID_Key, 65, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(65), 0 }, { &GUID_Key, 66, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(66), 0 }, { &GUID_Key, 67, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(67), 0 }, { &GUID_Key, 68, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(68), 0 }, { &GUID_Key, 69, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(69), 0 }, { &GUID_Key, 70, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(70), 0 }, { &GUID_Key, 71, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(71), 0 }, { &GUID_Key, 72, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(72), 0 }, { &GUID_Key, 73, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(73), 0 }, { &GUID_Key, 74, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(74), 0 }, { &GUID_Key, 75, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(75), 0 }, { &GUID_Key, 76, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(76), 0 }, { &GUID_Key, 77, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(77), 0 }, { &GUID_Key, 78, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(78), 0 }, { &GUID_Key, 79, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(79), 0 }, { &GUID_Key, 80, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(80), 0 }, { &GUID_Key, 81, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(81), 0 }, { &GUID_Key, 82, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(82), 0 }, { &GUID_Key, 83, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(83), 0 }, { &GUID_Key, 84, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(84), 0 }, { &GUID_Key, 85, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(85), 0 }, { &GUID_Key, 86, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(86), 0 }, { &GUID_Key, 87, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(87), 0 }, { &GUID_Key, 88, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(88), 0 }, { &GUID_Key, 89, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(89), 0 }, { &GUID_Key, 90, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(90), 0 }, { &GUID_Key, 91, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(91), 0 }, { &GUID_Key, 92, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(92), 0 }, { &GUID_Key, 93, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(93), 0 }, { &GUID_Key, 94, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(94), 0 }, { &GUID_Key, 95, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(95), 0 }, { &GUID_Key, 96, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(96), 0 }, { &GUID_Key, 97, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(97), 0 }, { &GUID_Key, 98, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(98), 0 }, { &GUID_Key, 99, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(99), 0 }, { &GUID_Key, 100, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(100), 0 }, { &GUID_Key, 101, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(101), 0 }, { &GUID_Key, 102, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(102), 0 }, { &GUID_Key, 103, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(103), 0 }, { &GUID_Key, 104, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(104), 0 }, { &GUID_Key, 105, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(105), 0 }, { &GUID_Key, 106, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(106), 0 }, { &GUID_Key, 107, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(107), 0 }, { &GUID_Key, 108, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(108), 0 }, { &GUID_Key, 109, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(109), 0 }, { &GUID_Key, 110, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(110), 0 }, { &GUID_Key, 111, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(111), 0 }, { &GUID_Key, 112, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(112), 0 }, { &GUID_Key, 113, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(113), 0 }, { &GUID_Key, 114, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(114), 0 }, { &GUID_Key, 115, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(115), 0 }, { &GUID_Key, 116, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(116), 0 }, { &GUID_Key, 117, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(117), 0 }, { &GUID_Key, 118, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(118), 0 }, { &GUID_Key, 119, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(119), 0 }, { &GUID_Key, 120, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(120), 0 }, { &GUID_Key, 121, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(121), 0 }, { &GUID_Key, 122, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(122), 0 }, { &GUID_Key, 123, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(123), 0 }, { &GUID_Key, 124, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(124), 0 }, { &GUID_Key, 125, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(125), 0 }, { &GUID_Key, 126, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(126), 0 }, { &GUID_Key, 127, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(127), 0 }, { &GUID_Key, 128, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(128), 0 }, { &GUID_Key, 129, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(129), 0 }, { &GUID_Key, 130, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(130), 0 }, { &GUID_Key, 131, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(131), 0 }, { &GUID_Key, 132, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(132), 0 }, { &GUID_Key, 133, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(133), 0 }, { &GUID_Key, 134, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(134), 0 }, { &GUID_Key, 135, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(135), 0 }, { &GUID_Key, 136, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(136), 0 }, { &GUID_Key, 137, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(137), 0 }, { &GUID_Key, 138, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(138), 0 }, { &GUID_Key, 139, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(139), 0 }, { &GUID_Key, 140, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(140), 0 }, { &GUID_Key, 141, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(141), 0 }, { &GUID_Key, 142, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(142), 0 }, { &GUID_Key, 143, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(143), 0 }, { &GUID_Key, 144, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(144), 0 }, { &GUID_Key, 145, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(145), 0 }, { &GUID_Key, 146, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(146), 0 }, { &GUID_Key, 147, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(147), 0 }, { &GUID_Key, 148, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(148), 0 }, { &GUID_Key, 149, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(149), 0 }, { &GUID_Key, 150, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(150), 0 }, { &GUID_Key, 151, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(151), 0 }, { &GUID_Key, 152, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(152), 0 }, { &GUID_Key, 153, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(153), 0 }, { &GUID_Key, 154, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(154), 0 }, { &GUID_Key, 155, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(155), 0 }, { &GUID_Key, 156, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(156), 0 }, { &GUID_Key, 157, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(157), 0 }, { &GUID_Key, 158, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(158), 0 }, { &GUID_Key, 159, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(159), 0 }, { &GUID_Key, 160, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(160), 0 }, { &GUID_Key, 161, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(161), 0 }, { &GUID_Key, 162, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(162), 0 }, { &GUID_Key, 163, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(163), 0 }, { &GUID_Key, 164, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(164), 0 }, { &GUID_Key, 165, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(165), 0 }, { &GUID_Key, 166, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(166), 0 }, { &GUID_Key, 167, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(167), 0 }, { &GUID_Key, 168, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(168), 0 }, { &GUID_Key, 169, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(169), 0 }, { &GUID_Key, 170, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(170), 0 }, { &GUID_Key, 171, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(171), 0 }, { &GUID_Key, 172, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(172), 0 }, { &GUID_Key, 173, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(173), 0 }, { &GUID_Key, 174, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(174), 0 }, { &GUID_Key, 175, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(175), 0 }, { &GUID_Key, 176, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(176), 0 }, { &GUID_Key, 177, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(177), 0 }, { &GUID_Key, 178, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(178), 0 }, { &GUID_Key, 179, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(179), 0 }, { &GUID_Key, 180, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(180), 0 }, { &GUID_Key, 181, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(181), 0 }, { &GUID_Key, 182, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(182), 0 }, { &GUID_Key, 183, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(183), 0 }, { &GUID_Key, 184, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(184), 0 }, { &GUID_Key, 185, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(185), 0 }, { &GUID_Key, 186, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(186), 0 }, { &GUID_Key, 187, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(187), 0 }, { &GUID_Key, 188, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(188), 0 }, { &GUID_Key, 189, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(189), 0 }, { &GUID_Key, 190, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(190), 0 }, { &GUID_Key, 191, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(191), 0 }, { &GUID_Key, 192, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(192), 0 }, { &GUID_Key, 193, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(193), 0 }, { &GUID_Key, 194, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(194), 0 }, { &GUID_Key, 195, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(195), 0 }, { &GUID_Key, 196, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(196), 0 }, { &GUID_Key, 197, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(197), 0 }, { &GUID_Key, 198, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(198), 0 }, { &GUID_Key, 199, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(199), 0 }, { &GUID_Key, 200, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(200), 0 }, { &GUID_Key, 201, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(201), 0 }, { &GUID_Key, 202, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(202), 0 }, { &GUID_Key, 203, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(203), 0 }, { &GUID_Key, 204, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(204), 0 }, { &GUID_Key, 205, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(205), 0 }, { &GUID_Key, 206, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(206), 0 }, { &GUID_Key, 207, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(207), 0 }, { &GUID_Key, 208, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(208), 0 }, { &GUID_Key, 209, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(209), 0 }, { &GUID_Key, 210, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(210), 0 }, { &GUID_Key, 211, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(211), 0 }, { &GUID_Key, 212, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(212), 0 }, { &GUID_Key, 213, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(213), 0 }, { &GUID_Key, 214, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(214), 0 }, { &GUID_Key, 215, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(215), 0 }, { &GUID_Key, 216, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(216), 0 }, { &GUID_Key, 217, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(217), 0 }, { &GUID_Key, 218, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(218), 0 }, { &GUID_Key, 219, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(219), 0 }, { &GUID_Key, 220, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(220), 0 }, { &GUID_Key, 221, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(221), 0 }, { &GUID_Key, 222, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(222), 0 }, { &GUID_Key, 223, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(223), 0 }, { &GUID_Key, 224, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(224), 0 }, { &GUID_Key, 225, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(225), 0 }, { &GUID_Key, 226, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(226), 0 }, { &GUID_Key, 227, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(227), 0 }, { &GUID_Key, 228, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(228), 0 }, { &GUID_Key, 229, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(229), 0 }, { &GUID_Key, 230, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(230), 0 }, { &GUID_Key, 231, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(231), 0 }, { &GUID_Key, 232, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(232), 0 }, { &GUID_Key, 233, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(233), 0 }, { &GUID_Key, 234, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(234), 0 }, { &GUID_Key, 235, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(235), 0 }, { &GUID_Key, 236, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(236), 0 }, { &GUID_Key, 237, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(237), 0 }, { &GUID_Key, 238, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(238), 0 }, { &GUID_Key, 239, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(239), 0 }, { &GUID_Key, 240, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(240), 0 }, { &GUID_Key, 241, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(241), 0 }, { &GUID_Key, 242, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(242), 0 }, { &GUID_Key, 243, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(243), 0 }, { &GUID_Key, 244, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(244), 0 }, { &GUID_Key, 245, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(245), 0 }, { &GUID_Key, 246, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(246), 0 }, { &GUID_Key, 247, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(247), 0 }, { &GUID_Key, 248, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(248), 0 }, { &GUID_Key, 249, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(249), 0 }, { &GUID_Key, 250, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(250), 0 }, { &GUID_Key, 251, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(251), 0 }, { &GUID_Key, 252, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(252), 0 }, { &GUID_Key, 253, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(253), 0 }, { &GUID_Key, 254, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(254), 0 }, { &GUID_Key, 255, DIDFT_OPTIONAL | DIDFT_BUTTON | DIDFT_MAKEINSTANCE(255), 0 }, }; // 0x4FDF20 static const DIDATAFORMAT c_dfDIKeyboard = { sizeof(DIDATAFORMAT), sizeof(DIOBJECTDATAFORMAT), DIDF_RELAXIS, 256, sizeof(dfDIKeyboard) / sizeof(dfDIKeyboard[0]), (LPDIOBJECTDATAFORMAT)dfDIKeyboard }; // 0x51E458 static LPDIRECTINPUTA lpDirectInput = NULL; // 0x51E45C static LPDIRECTINPUTDEVICEA lpDirectInputMouse = NULL; // 0x51E460 static LPDIRECTINPUTDEVICEA lpDirectInputKeyboard = NULL; // 0x51E464 static int nDirectInputKeyboardBufferIndex = 0; // 0x51E468 static int nDirectInputKeyboardBufferCount = 0; // 0x6B2560 static DIDEVICEOBJECTDATA DirectInputKeyboardBuffer[KEYBOARD_DEVICE_DATA_CAPACITY]; // 0x4E0400 bool dxinput_init() { if (lpDirectInput != NULL) { return false; } HRESULT hr = GNW95_DirectInputCreate(GNW95_hInstance, DIRECTINPUT_VERSION, &lpDirectInput, NULL); if (hr != DI_OK) { goto err; } if (!dxinput_mouse_init()) { goto err; } if (!dxinput_keyboard_init()) { goto err; } return true; err: dxinput_keyboard_exit(); dxinput_mouse_exit(); if (lpDirectInput != NULL) { IDirectInput_Release(lpDirectInput); lpDirectInput = NULL; } return false; } // 0x4E0478 void dxinput_exit() { // NOTE: Uninline. dxinput_keyboard_exit(); // NOTE: Uninline. dxinput_mouse_exit(); if (lpDirectInput != NULL) { IDirectInput_Release(lpDirectInput); lpDirectInput = NULL; } } // 0x4E04E8 bool dxinput_acquire_mouse() { if (lpDirectInputMouse == NULL) { return false; } HRESULT hr = IDirectInputDevice_Acquire(lpDirectInputMouse); if (hr != DI_OK && hr != S_FALSE) { return false; } return true; } // 0x4E0514 bool dxinput_unacquire_mouse() { if (lpDirectInputMouse == NULL) { return false; } HRESULT hr = IDirectInputDevice_Unacquire(lpDirectInputMouse); if (hr != DI_OK) { return false; } return true; } // 0x4E053C bool dxinput_get_mouse_state(dxinput_mouse_state* mouse_state) { if (lpDirectInputMouse == NULL) { return false; } if (!dxinput_acquire_mouse()) { return false; } DIMOUSESTATE dims; HRESULT hr = IDirectInputDevice_GetDeviceState(lpDirectInputMouse, sizeof(dims), &dims); if (hr != DI_OK) { return false; } mouse_state->delta_x = dims.lX; mouse_state->delta_y = dims.lY; mouse_state->left_button = (dims.rgbButtons[0] & 0x80) != 0; mouse_state->right_button = (dims.rgbButtons[1] & 0x80) != 0; return true; } // 0x4E05A8 bool dxinput_acquire_keyboard() { if (lpDirectInputKeyboard == NULL) { return false; } HRESULT hr = IDirectInputDevice_Acquire(lpDirectInputKeyboard); if (hr != DI_OK && hr != S_FALSE) { return false; } return true; } // 0x4E05D4 bool dxinput_unacquire_keyboard() { if (lpDirectInputKeyboard == NULL) { return false; } HRESULT hr = IDirectInputDevice_Unacquire(lpDirectInputKeyboard); if (hr != DI_OK) { return false; } return true; } // 0x4E05FC bool dxinput_flush_keyboard_buffer() { if (lpDirectInputKeyboard == NULL) { return false; } if (!dxinput_acquire_keyboard()) { return false; } DWORD items = -1; HRESULT hr = IDirectInputDevice_GetDeviceData(lpDirectInputKeyboard, sizeof(DIDEVICEOBJECTDATA), NULL, &items, 0); if (hr != DI_OK && hr != DI_BUFFEROVERFLOW) { return false; } return true; } // 0x4E0650 bool dxinput_read_keyboard_buffer(dxinput_key_data* key_data) { if (lpDirectInputKeyboard == NULL) { return false; } if (!dxinput_acquire_keyboard()) { return false; } if (nDirectInputKeyboardBufferIndex >= nDirectInputKeyboardBufferCount) { DWORD items = KEYBOARD_DEVICE_DATA_CAPACITY; HRESULT hr = IDirectInputDevice_GetDeviceData(lpDirectInputKeyboard, sizeof(DIDEVICEOBJECTDATA), DirectInputKeyboardBuffer, &items, 0); if (hr == DI_OK || hr == DI_BUFFEROVERFLOW) { nDirectInputKeyboardBufferCount = items; nDirectInputKeyboardBufferIndex = 0; } } if (nDirectInputKeyboardBufferIndex < nDirectInputKeyboardBufferCount) { DIDEVICEOBJECTDATA* entry = &(DirectInputKeyboardBuffer[nDirectInputKeyboardBufferIndex]); key_data->code = entry->dwOfs & 0xFF; key_data->state = (entry->dwData & 0x80) != 0; nDirectInputKeyboardBufferIndex++; return true; } return false; } // 0x4E070C static bool dxinput_mouse_init() { HRESULT hr; hr = IDirectInput_CreateDevice(lpDirectInput, &GUID_SysMouse, &lpDirectInputMouse, NULL); if (hr != DI_OK) { goto err; } hr = IDirectInputDevice_SetCooperativeLevel(lpDirectInputMouse, GNW95_hwnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND); if (hr != DI_OK) { goto err; } hr = IDirectInputDevice_SetDataFormat(lpDirectInputMouse, &c_dfDIMouse); if (hr != DI_OK) { goto err; } return true; err: // NOTE: Uninline. dxinput_mouse_exit(); return false; } // 0x4E078C static void dxinput_mouse_exit() { if (lpDirectInputMouse != NULL) { IDirectInputDevice_Unacquire(lpDirectInputMouse); IDirectInputDevice_Release(lpDirectInputMouse); lpDirectInputMouse = NULL; } } // 0x4E07B8 static bool dxinput_keyboard_init() { HRESULT hr; hr = IDirectInput_CreateDevice(lpDirectInput, &GUID_SysKeyboard, &lpDirectInputKeyboard, NULL); if (hr != DI_OK) { goto err; } hr = IDirectInputDevice_SetCooperativeLevel(lpDirectInputKeyboard, GNW95_hwnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND); if (hr != DI_OK) { goto err; } hr = IDirectInputDevice_SetDataFormat(lpDirectInputKeyboard, &c_dfDIKeyboard); if (hr != DI_OK) { goto err; } DIPROPDWORD dipdw; dipdw.diph.dwSize = sizeof(DIPROPDWORD); dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER); dipdw.diph.dwObj = 0; dipdw.diph.dwHow = DIPH_DEVICE; dipdw.dwData = KEYBOARD_DEVICE_DATA_CAPACITY; hr = IDirectInputDevice_SetProperty(lpDirectInputKeyboard, DIPROP_BUFFERSIZE, &(dipdw.diph)); if (hr != DI_OK) { goto err; } return true; err: // NOTE: Uninline. dxinput_keyboard_exit(); return false; } // 0x4E0874 static void dxinput_keyboard_exit() { if (lpDirectInputKeyboard != NULL) { IDirectInputDevice_Unacquire(lpDirectInputKeyboard); IDirectInputDevice_Release(lpDirectInputKeyboard); lpDirectInputKeyboard = NULL; } } ================================================ FILE: src/plib/gnw/dxinput.h ================================================ #ifndef FALLOUT_PLIB_GNW_DXINPUT_H_ #define FALLOUT_PLIB_GNW_DXINPUT_H_ #include typedef struct dxinput_mouse_state { int delta_x; int delta_y; unsigned char left_button; unsigned char right_button; } dxinput_mouse_state; typedef struct dxinput_key_data { unsigned char code; unsigned char state; } dxinput_key_data; bool dxinput_init(); void dxinput_exit(); bool dxinput_acquire_mouse(); bool dxinput_unacquire_mouse(); bool dxinput_get_mouse_state(dxinput_mouse_state* mouse_state); bool dxinput_acquire_keyboard(); bool dxinput_unacquire_keyboard(); bool dxinput_flush_keyboard_buffer(); bool dxinput_read_keyboard_buffer(dxinput_key_data* key_data); #endif /* FALLOUT_PLIB_GNW_DXINPUT_H_ */ ================================================ FILE: src/plib/gnw/gnw.c ================================================ #include "plib/gnw/gnw.h" #include "plib/color/color.h" #include "plib/gnw/input.h" #include "plib/db/db.h" #include "plib/gnw/button.h" #include "plib/gnw/debug.h" #include "plib/gnw/grbuf.h" #include "plib/gnw/memory.h" #include "game/palette.h" #include "plib/gnw/text.h" #include "plib/gnw/vcr.h" #include "plib/gnw/intrface.h" #include "plib/gnw/svga.h" #include "plib/gnw/winmain.h" static void win_free(int win); static void win_clip(Window* window, RectPtr* rectListNodePtr, unsigned char* a3); static void refresh_all(Rect* rect, unsigned char* a2); static int colorOpen(const char* path, int flags); static int colorRead(int fd, void* buf, size_t count); static int colorClose(int fd); // 0x51E3D8 static bool GNW95_already_running = false; // 0x51E3DC static HANDLE GNW95_title_mutex = INVALID_HANDLE_VALUE; // 0x51E3E0 bool GNW_win_init_flag = false; // 0x51E3E4 int GNW_wcolor[6] = { 0, 0, 0, 0, 0, 0, }; // 0x51E3FC static unsigned char* screen_buffer = NULL; // 0x6ADD90 static int window_index[MAX_WINDOW_COUNT]; // 0x6ADE58 static Window* window[MAX_WINDOW_COUNT]; // 0x6ADF20 static VideoSystemExitProc* video_reset; // 0x6ADF24 static int num_windows; // 0x6ADF28 static int window_flags; // 0x6ADF2C static bool buffering; // 0x6ADF30 static int bk_color; // 0x6ADF34 static VideoSystemInitProc* video_set; // 0x6ADF38 static int doing_refresh_all; // 0x6ADF3C void* GNW_texture; // 0x4D5C30 int win_init(VideoSystemInitProc* videoSystemInitProc, VideoSystemExitProc* videoSystemExitProc, int a3) { CloseHandle(GNW95_mutex); GNW95_mutex = INVALID_HANDLE_VALUE; if (GNW95_already_running) { return WINDOW_MANAGER_ERR_ALREADY_RUNNING; } if (GNW95_title_mutex == INVALID_HANDLE_VALUE) { return WINDOW_MANAGER_ERR_TITLE_NOT_SET; } if (GNW_win_init_flag) { return WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED; } for (int index = 0; index < MAX_WINDOW_COUNT; index++) { window_index[index] = -1; } if (db_total() == 0) { if (db_init(NULL, 0, "", 1) == -1) { return WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE; } } if (GNW_text_init() == -1) { return WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS; } reset_mode(); video_set = videoSystemInitProc; video_reset = GNW95_reset_mode; int rc = videoSystemInitProc(); if (rc == -1) { if (video_reset != NULL) { video_reset(); } return WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE; } if (rc == 8) { return WINDOW_MANAGER_ERR_8; } if (a3 & 1) { screen_buffer = (unsigned char*)mem_malloc((scr_size.lry - scr_size.uly + 1) * (scr_size.lrx - scr_size.ulx + 1)); if (screen_buffer == NULL) { if (video_reset != NULL) { video_reset(); } else { GNW95_reset_mode(); } return WINDOW_MANAGER_ERR_NO_MEMORY; } } buffering = false; doing_refresh_all = 0; colorInitIO(colorOpen, colorRead, colorClose); colorRegisterAlloc(mem_malloc, mem_realloc, mem_free); if (!initColors()) { unsigned char* palette = (unsigned char*)mem_malloc(768); if (palette == NULL) { if (video_reset != NULL) { video_reset(); } else { GNW95_reset_mode(); } if (screen_buffer != NULL) { mem_free(screen_buffer); } return WINDOW_MANAGER_ERR_NO_MEMORY; } buf_fill(palette, 768, 1, 768, 0); // TODO: Incomplete. // _colorBuildColorTable(getSystemPalette(), palette); mem_free(palette); } GNW_debug_init(); if (GNW_input_init(a3) == -1) { return WINDOW_MANAGER_ERR_INITIALIZING_INPUT; } GNW_intr_init(); Window* w = window[0] = (Window*)mem_malloc(sizeof(*w)); if (w == NULL) { if (video_reset != NULL) { video_reset(); } else { GNW95_reset_mode(); } if (screen_buffer != NULL) { mem_free(screen_buffer); } return WINDOW_MANAGER_ERR_NO_MEMORY; } w->id = 0; w->flags = 0; w->rect.ulx = scr_size.ulx; w->rect.uly = scr_size.uly; w->rect.lrx = scr_size.lrx; w->rect.lry = scr_size.lry; w->width = scr_size.lrx - scr_size.ulx + 1; w->height = scr_size.lry - scr_size.uly + 1; w->field_24 = 0; w->field_28 = 0; w->buffer = NULL; w->buttonListHead = NULL; w->field_34 = NULL; w->field_38 = 0; w->menuBar = NULL; num_windows = 1; GNW_win_init_flag = 1; GNW_wcolor[3] = 21140; GNW_wcolor[4] = 32747; GNW_wcolor[5] = 31744; window_index[0] = 0; GNW_texture = NULL; bk_color = 0; GNW_wcolor[0] = 10570; window_flags = a3; GNW_wcolor[2] = 8456; GNW_wcolor[1] = 15855; atexit(win_exit); return WINDOW_MANAGER_OK; } // 0x4D616C void win_exit(void) { // 0x51E400 static bool insideWinExit = false; if (!insideWinExit) { insideWinExit = true; if (GNW_win_init_flag) { GNW_intr_exit(); for (int index = num_windows - 1; index >= 0; index--) { win_free(window[index]->id); } if (GNW_texture != NULL) { mem_free(GNW_texture); } if (screen_buffer != NULL) { mem_free(screen_buffer); } if (video_reset != NULL) { video_reset(); } GNW_input_exit(); GNW_rect_exit(); GNW_text_exit(); colorsClose(); GNW_win_init_flag = false; CloseHandle(GNW95_title_mutex); GNW95_title_mutex = INVALID_HANDLE_VALUE; } insideWinExit = false; } } // win_add // 0x4D6238 int win_add(int x, int y, int width, int height, int a4, int flags) { int v23; int v25, v26; Window* tmp; if (!GNW_win_init_flag) { return -1; } if (num_windows == MAX_WINDOW_COUNT) { return -1; } if (width > rectGetWidth(&scr_size)) { return -1; } if (height > rectGetHeight(&scr_size)) { return -1; } Window* w = window[num_windows] = (Window*)mem_malloc(sizeof(*w)); if (w == NULL) { return -1; } w->buffer = (unsigned char*)mem_malloc(width * height); if (w->buffer == NULL) { mem_free(w); return -1; } int index = 1; while (GNW_find(index) != NULL) { index++; } w->id = index; if ((flags & WINDOW_FLAG_0x01) != 0) { flags |= window_flags; } w->width = width; w->height = height; w->flags = flags; w->field_24 = rand() & 0xFFFE; w->field_28 = rand() & 0xFFFE; if (a4 == 256) { if (GNW_texture == NULL) { a4 = colorTable[GNW_wcolor[0]]; } } else if ((a4 & 0xFF00) != 0) { int colorIndex = (a4 & 0xFF) - 1; a4 = (a4 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]]; } w->buttonListHead = 0; w->field_34 = 0; w->field_38 = 0; w->menuBar = NULL; w->blitProc = trans_buf_to_buf; w->field_20 = a4; window_index[index] = num_windows; num_windows++; win_fill(index, 0, 0, width, height, a4); w->flags |= WINDOW_HIDDEN; win_move(index, x, y); w->flags = flags; if ((flags & WINDOW_FLAG_0x04) == 0) { v23 = num_windows - 2; while (v23 > 0) { if (!(window[v23]->flags & WINDOW_FLAG_0x04)) { break; } v23--; } if (v23 != num_windows - 2) { v25 = v23 + 1; v26 = num_windows - 1; while (v26 > v25) { tmp = window[v26 - 1]; window[v26] = tmp; window_index[tmp->id] = v26; v26--; } window[v25] = w; window_index[index] = v25; } } return index; } // 0x4D6468 void win_delete(int win) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } Rect rect; rectCopy(&rect, &(w->rect)); int v1 = window_index[w->id]; win_free(win); window_index[win] = -1; for (int index = v1; index < num_windows - 1; index++) { window[index] = window[index + 1]; window_index[window[index]->id] = index; } num_windows--; // NOTE: Uninline. win_refresh_all(&rect); } // 0x4D650C static void win_free(int win) { Window* w = GNW_find(win); if (w == NULL) { return; } if (w->buffer != NULL) { mem_free(w->buffer); } if (w->menuBar != NULL) { mem_free(w->menuBar); } Button* curr = w->buttonListHead; while (curr != NULL) { Button* next = curr->next; GNW_delete_button(curr); curr = next; } mem_free(w); } // 0x4D6558 void win_buffering(bool a1) { if (screen_buffer != NULL) { buffering = a1; } } // 0x4D6568 void win_border(int win) { if (!GNW_win_init_flag) { return; } Window* w = GNW_find(win); if (w == NULL) { return; } lighten_buf(w->buffer + 5, w->width - 10, 5, w->width); lighten_buf(w->buffer, 5, w->height, w->width); lighten_buf(w->buffer + w->width - 5, 5, w->height, w->width); lighten_buf(w->buffer + w->width * (w->height - 5) + 5, w->width - 10, 5, w->width); draw_box(w->buffer, w->width, 0, 0, w->width - 1, w->height - 1, colorTable[0]); draw_shaded_box(w->buffer, w->width, 1, 1, w->width - 2, w->height - 2, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]); draw_shaded_box(w->buffer, w->width, 5, 5, w->width - 6, w->height - 6, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); } // 0x4D684C void win_print(int win, char* str, int a3, int x, int y, int a6) { int v7; int v14; unsigned char* buf; int v27; Window* w = GNW_find(win); v7 = a3; if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } if (a3 == 0) { if (a6 & 0x040000) { v7 = text_mono_width(str); } else { v7 = text_width(str); } } if (v7 + x > w->width) { if (!(a6 & 0x04000000)) { return; } v7 = w->width - x; } buf = w->buffer + x + y * w->width; v14 = text_height(); if (v14 + y > w->height) { return; } if (!(a6 & 0x02000000)) { if (w->field_20 == 256 && GNW_texture != NULL) { buf_texture(buf, v7, text_height(), w->width, GNW_texture, w->field_24 + x, w->field_28 + y); } else { buf_fill(buf, v7, text_height(), w->width, w->field_20); } } if ((a6 & 0xFF00) != 0) { int colorIndex = (a6 & 0xFF) - 1; v27 = (a6 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]]; } else { v27 = a6; } text_to_buf(buf, str, v7, w->width, v27); if (a6 & 0x01000000) { // TODO: Check. Rect rect; rect.ulx = w->rect.ulx + x; rect.uly = w->rect.uly + y; rect.lrx = rect.ulx + v7; rect.lry = rect.uly + text_height(); GNW_win_refresh(w, &rect, NULL); } } // 0x4D69DC void win_text(int win, char** fileNameList, int fileNameListLength, int maxWidth, int x, int y, int flags) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } int width = w->width; unsigned char* ptr = w->buffer + y * width + x; int lineHeight = text_height(); int step = width * lineHeight; int v1 = lineHeight / 2; int v2 = v1 + 1; int v3 = maxWidth - 1; for (int index = 0; index < fileNameListLength; index++) { char* fileName = fileNameList[index]; if (*fileName != '\0') { win_print(win, fileName, maxWidth, x, y, flags); } else { if (maxWidth != 0) { draw_line(ptr, width, 0, v1, v3, v1, colorTable[GNW_wcolor[2]]); draw_line(ptr, width, 0, v2, v3, v2, colorTable[GNW_wcolor[1]]); } } ptr += step; y += lineHeight; } } // 0x4D6B24 void win_line(int win, int left, int top, int right, int bottom, int color) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } if ((color & 0xFF00) != 0) { int colorIndex = (color & 0xFF) - 1; color = (color & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]]; } draw_line(w->buffer, w->width, left, top, right, bottom, color); } // 0x4D6B88 void win_box(int win, int left, int top, int right, int bottom, int color) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } if ((color & 0xFF00) != 0) { int colorIndex = (color & 0xFF) - 1; color = (color & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]]; } if (right < left) { int tmp = left; left = right; right = tmp; } if (bottom < top) { int tmp = top; top = bottom; bottom = tmp; } draw_box(w->buffer, w->width, left, top, right, bottom, color); } // 0x4D6CC8 void win_fill(int win, int x, int y, int width, int height, int a6) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } if (a6 == 256) { if (GNW_texture != NULL) { buf_texture(w->buffer + w->width * y + x, width, height, w->width, GNW_texture, x + w->field_24, y + w->field_28); } else { a6 = colorTable[GNW_wcolor[0]] & 0xFF; } } else if ((a6 & 0xFF00) != 0) { int colorIndex = (a6 & 0xFF) - 1; a6 = (a6 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]]; } if (a6 < 256) { buf_fill(w->buffer + w->width * y + x, width, height, w->width, a6); } } // 0x4D6DAC void win_show(int win) { Window* w; int v3; int v5; int v7; Window* v6; w = GNW_find(win); v3 = window_index[w->id]; if (!GNW_win_init_flag) { return; } if (w->flags & WINDOW_HIDDEN) { w->flags &= ~WINDOW_HIDDEN; if (v3 == num_windows - 1) { GNW_win_refresh(w, &(w->rect), NULL); } } v5 = num_windows - 1; if (v3 < v5 && !(w->flags & WINDOW_FLAG_0x02)) { v7 = v3; while (v3 < v5 && ((w->flags & WINDOW_FLAG_0x04) || !(window[v7 + 1]->flags & WINDOW_FLAG_0x04))) { v6 = window[v7 + 1]; window[v7] = v6; v7++; window_index[v6->id] = v3++; } window[v3] = w; window_index[w->id] = v3; GNW_win_refresh(w, &(w->rect), NULL); } } // 0x4D6E64 void win_hide(int win) { if (!GNW_win_init_flag) { return; } Window* w = GNW_find(win); if (w == NULL) { return; } if ((w->flags & WINDOW_HIDDEN) == 0) { w->flags |= WINDOW_HIDDEN; refresh_all(&(w->rect), NULL); } } // 0x4D6EA0 void win_move(int win, int x, int y) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } Rect rect; rectCopy(&rect, &(w->rect)); if (x < 0) { x = 0; } if (y < 0) { y = 0; } if ((w->flags & WINDOW_FLAG_0x0100) != 0) { x += 2; } if (x + w->width - 1 > scr_size.lrx) { x = scr_size.lrx - w->width + 1; } if (y + w->height - 1 > scr_size.lry) { y = scr_size.lry - w->height + 1; } if ((w->flags & WINDOW_FLAG_0x0100) != 0) { // TODO: Not sure what this means. x &= ~0x03; } w->rect.ulx = x; w->rect.uly = y; w->rect.lrx = w->width + x - 1; w->rect.lry = w->height + y - 1; if ((w->flags & WINDOW_HIDDEN) == 0) { GNW_win_refresh(w, &(w->rect), NULL); if (GNW_win_init_flag) { refresh_all(&rect, NULL); } } } // 0x4D6F5C void win_draw(int win) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } GNW_win_refresh(w, &(w->rect), NULL); } // 0x4D6F80 void win_draw_rect(int win, const Rect* rect) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } Rect newRect; rectCopy(&newRect, rect); rectOffset(&newRect, w->rect.ulx, w->rect.uly); GNW_win_refresh(w, &newRect, NULL); } // 0x4D6FD8 void GNW_win_refresh(Window* w, Rect* rect, unsigned char* a3) { RectPtr v26, v20, v23, v24; int dest_pitch; // TODO: Get rid of this. dest_pitch = 0; if ((w->flags & WINDOW_HIDDEN) != 0) { return; } if ((w->flags & WINDOW_FLAG_0x20) && buffering && !doing_refresh_all) { // TODO: Incomplete. } else { v26 = rect_malloc(); if (v26 == NULL) { return; } v26->next = NULL; v26->rect.ulx = max(w->rect.ulx, rect->ulx); v26->rect.uly = max(w->rect.uly, rect->uly); v26->rect.lrx = min(w->rect.lrx, rect->lrx); v26->rect.lry = min(w->rect.lry, rect->lry); if (v26->rect.lrx >= v26->rect.ulx && v26->rect.lry >= v26->rect.uly) { if (a3) { dest_pitch = rect->lrx - rect->ulx + 1; } win_clip(w, &v26, a3); if (w->id) { v20 = v26; while (v20) { GNW_button_refresh(w, &(v20->rect)); if (a3) { if (buffering && (w->flags & WINDOW_FLAG_0x20)) { w->blitProc(w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width, v20->rect.lrx - v20->rect.ulx + 1, v20->rect.lry - v20->rect.uly + 1, w->width, a3 + dest_pitch * (v20->rect.uly - rect->uly) + v20->rect.ulx - rect->ulx, dest_pitch); } else { buf_to_buf( w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width, v20->rect.lrx - v20->rect.ulx + 1, v20->rect.lry - v20->rect.uly + 1, w->width, a3 + dest_pitch * (v20->rect.uly - rect->uly) + v20->rect.ulx - rect->ulx, dest_pitch); } } else { if (buffering) { if (w->flags & WINDOW_FLAG_0x20) { w->blitProc( w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width, v20->rect.lrx - v20->rect.ulx + 1, v20->rect.lry - v20->rect.uly + 1, w->width, screen_buffer + v20->rect.uly * (scr_size.lrx - scr_size.ulx + 1) + v20->rect.ulx, scr_size.lrx - scr_size.ulx + 1); } else { buf_to_buf( w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width, v20->rect.lrx - v20->rect.ulx + 1, v20->rect.lry - v20->rect.uly + 1, w->width, screen_buffer + v20->rect.uly * (scr_size.lrx - scr_size.ulx + 1) + v20->rect.ulx, scr_size.lrx - scr_size.ulx + 1); } } else { scr_blit( w->buffer + v20->rect.ulx - w->rect.ulx + (v20->rect.uly - w->rect.uly) * w->width, w->width, v20->rect.lry - v20->rect.lry + 1, 0, 0, v20->rect.lrx - v20->rect.ulx + 1, v20->rect.lry - v20->rect.uly + 1, v20->rect.ulx, v20->rect.uly); } } v20 = v20->next; } } else { rectdata* v16 = v26; while (v16 != NULL) { int width = v16->rect.lrx - v16->rect.ulx + 1; int height = v16->rect.lry - v16->rect.uly + 1; unsigned char* buf = (unsigned char*)mem_malloc(width * height); if (buf != NULL) { buf_fill(buf, width, height, width, bk_color); if (dest_pitch != 0) { buf_to_buf( buf, width, height, width, a3 + dest_pitch * (v16->rect.uly - rect->uly) + v16->rect.ulx - rect->ulx, dest_pitch); } else { if (buffering) { buf_to_buf(buf, width, height, width, screen_buffer + v16->rect.uly * (scr_size.lrx - scr_size.ulx + 1) + v16->rect.ulx, scr_size.lrx - scr_size.ulx + 1); } else { scr_blit(buf, width, height, 0, 0, width, height, v16->rect.ulx, v16->rect.uly); } } mem_free(buf); } v16 = v16->next; } } v23 = v26; while (v23) { v24 = v23->next; if (buffering && !a3) { scr_blit( screen_buffer + v23->rect.ulx + (scr_size.lrx - scr_size.ulx + 1) * v23->rect.uly, scr_size.lrx - scr_size.ulx + 1, v23->rect.lry - v23->rect.uly + 1, 0, 0, v23->rect.lrx - v23->rect.ulx + 1, v23->rect.lry - v23->rect.uly + 1, v23->rect.ulx, v23->rect.uly); } rect_free(v23); v23 = v24; } if (!doing_refresh_all && a3 == NULL && mouse_hidden() == 0) { if (mouse_in(rect->ulx, rect->uly, rect->lrx, rect->lry)) { mouse_show(); } } } else { rect_free(v26); } } } // 0x4D759C void win_refresh_all(Rect* rect) { if (GNW_win_init_flag) { refresh_all(rect, NULL); } } // 0x4D75B0 static void win_clip(Window* w, RectPtr* rectListNodePtr, unsigned char* a3) { int win; for (win = window_index[w->id] + 1; win < num_windows; win++) { if (*rectListNodePtr == NULL) { break; } // TODO: Review. Window* w = window[win]; if (!(w->flags & WINDOW_HIDDEN)) { if (!buffering || !(w->flags & WINDOW_FLAG_0x20)) { rect_clip_list(rectListNodePtr, &(w->rect)); } else { if (!doing_refresh_all) { GNW_win_refresh(w, &(w->rect), NULL); rect_clip_list(rectListNodePtr, &(w->rect)); } } } } if (a3 == screen_buffer || a3 == NULL) { if (mouse_hidden() == 0) { Rect rect; mouse_get_rect(&rect); rect_clip_list(rectListNodePtr, &rect); } } } // 0x4D765C void win_drag(int win) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return; } if (w == NULL) { return; } win_show(win); Rect rect; rectCopy(&rect, &(w->rect)); GNW_do_bk_process(); if (vcr_update() != 3) { mouse_info(); } if ((w->flags & WINDOW_FLAG_0x0100) && (w->rect.ulx & 3)) { win_move(w->id, w->rect.ulx, w->rect.uly); } } // 0x4D77F8 void win_get_mouse_buf(unsigned char* a1) { Rect rect; mouse_get_rect(&rect); refresh_all(&rect, a1); } // 0x4D7814 static void refresh_all(Rect* rect, unsigned char* a2) { doing_refresh_all = 1; for (int index = 0; index < num_windows; index++) { GNW_win_refresh(window[index], rect, a2); } doing_refresh_all = 0; if (a2 == NULL) { if (!mouse_hidden()) { if (mouse_in(rect->ulx, rect->uly, rect->lrx, rect->lry)) { mouse_show(); } } } } // 0x4D7888 Window* GNW_find(int win) { int v0; if (win == -1) { return NULL; } v0 = window_index[win]; if (v0 == -1) { return NULL; } return window[v0]; } // win_get_buf // 0x4D78B0 unsigned char* win_get_buf(int win) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return NULL; } if (w == NULL) { return NULL; } return w->buffer; } // 0x4D78CC int win_get_top_win(int x, int y) { for (int index = num_windows - 1; index >= 0; index--) { Window* w = window[index]; if (x >= w->rect.ulx && x <= w->rect.lrx && y >= w->rect.uly && y <= w->rect.lry) { return w->id; } } return -1; } // 0x4D7918 int win_width(int win) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (w == NULL) { return -1; } return w->width; } // 0x4D7934 int win_height(int win) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (w == NULL) { return -1; } return w->height; } // 0x4D7950 int win_get_rect(int win, Rect* rect) { Window* w = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (w == NULL) { return -1; } rectCopy(rect, &(w->rect)); return 0; } // 0x4D797C int win_check_all_buttons() { if (!GNW_win_init_flag) { return -1; } int v1 = -1; for (int index = num_windows - 1; index >= 1; index--) { if (GNW_check_buttons(window[index], &v1) == 0) { break; } if ((window[index]->flags & WINDOW_FLAG_0x10) != 0) { break; } } return v1; } // 0x4D79DC Button* GNW_find_button(int btn, Window** windowPtr) { for (int index = 0; index < num_windows; index++) { Window* w = window[index]; Button* button = w->buttonListHead; while (button != NULL) { if (button->id == btn) { if (windowPtr != NULL) { *windowPtr = w; } return button; } button = button->next; } } return NULL; } // 0x4D7A34 int GNW_check_menu_bars(int a1) { if (!GNW_win_init_flag) { return -1; } int v1 = a1; for (int index = num_windows - 1; index >= 1; index--) { Window* w = window[index]; if (w->menuBar != NULL) { for (int pulldownIndex = 0; pulldownIndex < w->menuBar->pulldownsLength; pulldownIndex++) { if (v1 == w->menuBar->pulldowns[pulldownIndex].keyCode) { v1 = GNW_process_menu(w->menuBar, pulldownIndex); break; } } } if ((w->flags & 0x10) != 0) { break; } } return v1; } // 0x4D80D8 void win_set_minimized_title(const char* title) { if (title == NULL) { return; } if (GNW95_title_mutex == INVALID_HANDLE_VALUE) { GNW95_title_mutex = CreateMutexA(NULL, TRUE, title); if (GetLastError() != ERROR_SUCCESS) { GNW95_already_running = true; return; } } strncpy(GNW95_title, title, 256); GNW95_title[256 - 1] = '\0'; if (GNW95_hwnd != NULL) { SetWindowTextA(GNW95_hwnd, GNW95_title); } } // [open] implementation for palette operations backed by [XFile]. // // 0x4D8174 static int colorOpen(const char* path, int flags) { char mode[4]; memset(mode, 0, sizeof(mode)); if ((flags & 0x01) != 0) { mode[0] = 'w'; } else if ((flags & 0x10) != 0) { mode[0] = 'a'; } else { mode[0] = 'r'; } if ((flags & 0x100) != 0) { mode[1] = 't'; } else if ((flags & 0x200) != 0) { mode[1] = 'b'; } File* stream = db_fopen(path, mode); if (stream != NULL) { return (int)stream; } return -1; } // [read] implementation for palette file operations backed by [XFile]. // // 0x4D81E8 static int colorRead(int fd, void* buf, size_t count) { return db_fread(buf, 1, count, (File*)fd); } // [close] implementation for palette file operations backed by [XFile]. // // 0x4D81E0 static int colorClose(int fd) { return db_fclose((File*)fd); } // 0x4D8200 bool GNWSystemError(const char* text) { HCURSOR cursor = LoadCursorA(GNW95_hInstance, MAKEINTRESOURCEA(IDC_ARROW)); HCURSOR prev = SetCursor(cursor); ShowCursor(TRUE); MessageBoxA(NULL, text, NULL, MB_ICONSTOP); ShowCursor(FALSE); SetCursor(prev); return true; } ================================================ FILE: src/plib/gnw/gnw.h ================================================ #ifndef FALLOUT_PLIB_GNW_GNW_H_ #define FALLOUT_PLIB_GNW_GNW_H_ #include #include #define WIN32_LEAN_AND_MEAN #include #include "plib/gnw/gnw_types.h" #include "plib/gnw/rect.h" #define MAX_WINDOW_COUNT (50) // The maximum number of radio groups. #define RADIO_GROUP_LIST_CAPACITY (64) typedef enum WindowManagerErr { WINDOW_MANAGER_OK = 0, WINDOW_MANAGER_ERR_INITIALIZING_VIDEO_MODE = 1, WINDOW_MANAGER_ERR_NO_MEMORY = 2, WINDOW_MANAGER_ERR_INITIALIZING_TEXT_FONTS = 3, WINDOW_MANAGER_ERR_WINDOW_SYSTEM_ALREADY_INITIALIZED = 4, WINDOW_MANAGER_ERR_WINDOW_SYSTEM_NOT_INITIALIZED = 5, WINDOW_MANAGER_ERR_CURRENT_WINDOWS_TOO_BIG = 6, WINDOW_MANAGER_ERR_INITIALIZING_DEFAULT_DATABASE = 7, // Unknown fatal error. // // NOTE: When this error code returned from window system initialization, the // game simply exits without any debug message. There is no way to figure out // it's meaning. WINDOW_MANAGER_ERR_8 = 8, WINDOW_MANAGER_ERR_ALREADY_RUNNING = 9, WINDOW_MANAGER_ERR_TITLE_NOT_SET = 10, WINDOW_MANAGER_ERR_INITIALIZING_INPUT = 11, } WindowManagerErr; typedef int(VideoSystemInitProc)(); typedef void(VideoSystemExitProc)(); extern bool GNW_win_init_flag; extern int GNW_wcolor[6]; extern unsigned char* screen_buffer; extern void* GNW_texture; int win_init(VideoSystemInitProc* videoSystemInitProc, VideoSystemExitProc* videoSystemExitProc, int a3); void win_exit(void); int win_add(int x, int y, int width, int height, int a4, int flags); void win_delete(int win); void win_buffering(bool a1); void win_border(int win); void win_print(int win, char* str, int a3, int x, int y, int a6); void win_text(int win, char** fileNameList, int fileNameListLength, int maxWidth, int x, int y, int flags); void win_line(int win, int left, int top, int right, int bottom, int color); void win_box(int win, int left, int top, int right, int bottom, int color); void win_fill(int win, int x, int y, int width, int height, int a6); void win_show(int win); void win_hide(int win); void win_move(int win_index, int x, int y); void win_draw(int win); void win_draw_rect(int win, const Rect* rect); void GNW_win_refresh(Window* window, Rect* rect, unsigned char* a3); void win_refresh_all(Rect* rect); void win_drag(int win); void win_get_mouse_buf(unsigned char* a1); Window* GNW_find(int win); unsigned char* win_get_buf(int win); int win_get_top_win(int x, int y); int win_width(int win); int win_height(int win); int win_get_rect(int win, Rect* rect); int win_check_all_buttons(); Button* GNW_find_button(int btn, Window** out_win); int GNW_check_menu_bars(int a1); void win_set_minimized_title(const char* title); bool GNWSystemError(const char* str); #endif /* FALLOUT_PLIB_GNW_GNW_H_ */ ================================================ FILE: src/plib/gnw/gnw95dx.c ================================================ #include "plib/gnw/gnw95dx.h" // 0x53A274 PFNDDRAWCREATE GNW95_DirectDrawCreate = NULL; // 0x53A278 PFNDINPUTCREATE GNW95_DirectInputCreate = NULL; // 0x53A27C PFNDSOUNDCREATE GNW95_DirectSoundCreate = NULL; ================================================ FILE: src/plib/gnw/gnw95dx.h ================================================ #ifndef FALLOUT_PLIB_GNW_GNW95DX_H_ #define FALLOUT_PLIB_GNW_GNW95DX_H_ #define WIN32_LEAN_AND_MEAN #include #define DIRECTDRAW_VERSION 0x0300 #include #define DIRECTINPUT_VERSION 0x0300 #include #include #define DIRECTSOUND_VERSION 0x0300 #include typedef HRESULT(__stdcall *PFNDDRAWCREATE)(GUID*, LPDIRECTDRAW*, IUnknown*); typedef HRESULT(__stdcall *PFNDINPUTCREATE)(HINSTANCE, DWORD, LPDIRECTINPUTA*, IUnknown*); typedef HRESULT(__stdcall *PFNDSOUNDCREATE)(GUID*, LPDIRECTSOUND*, IUnknown*); extern PFNDDRAWCREATE GNW95_DirectDrawCreate; extern PFNDINPUTCREATE GNW95_DirectInputCreate; extern PFNDSOUNDCREATE GNW95_DirectSoundCreate; #endif /* FALLOUT_PLIB_GNW_GNW95DX_H_ */ ================================================ FILE: src/plib/gnw/gnw_types.h ================================================ #ifndef FALLOUT_PLIB_GNW_GNW_TYPES_H_ #define FALLOUT_PLIB_GNW_GNW_TYPES_H_ #include "plib/gnw/rect.h" // The maximum number of buttons in one radio group. #define RADIO_GROUP_BUTTON_LIST_CAPACITY 64 typedef enum WindowFlags { WINDOW_FLAG_0x01 = 0x01, WINDOW_FLAG_0x02 = 0x02, WINDOW_FLAG_0x04 = 0x04, WINDOW_HIDDEN = 0x08, WINDOW_FLAG_0x10 = 0x10, WINDOW_FLAG_0x20 = 0x20, WINDOW_FLAG_0x40 = 0x40, WINDOW_FLAG_0x80 = 0x80, WINDOW_FLAG_0x0100 = 0x0100, } WindowFlags; typedef enum ButtonFlags { BUTTON_FLAG_0x01 = 0x01, BUTTON_FLAG_0x02 = 0x02, BUTTON_FLAG_0x04 = 0x04, BUTTON_FLAG_DISABLED = 0x08, BUTTON_FLAG_0x10 = 0x10, BUTTON_FLAG_TRANSPARENT = 0x20, BUTTON_FLAG_0x40 = 0x40, BUTTON_FLAG_0x010000 = 0x010000, BUTTON_FLAG_0x020000 = 0x020000, BUTTON_FLAG_0x040000 = 0x040000, BUTTON_FLAG_RIGHT_MOUSE_BUTTON_CONFIGURED = 0x080000, } ButtonFlags; typedef struct Button Button; typedef struct RadioGroup RadioGroup; typedef void WindowBlitProc(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch); typedef void ButtonCallback(int btn, int keyCode); typedef struct MenuPulldown { Rect rect; int keyCode; int itemsLength; char** items; int field_1C; int field_20; } MenuPulldown; typedef struct MenuBar { int win; Rect rect; int pulldownsLength; MenuPulldown pulldowns[15]; int borderColor; int backgroundColor; } MenuBar; static_assert(sizeof(MenuBar) == 572, "wrong size"); typedef struct Window { int id; int flags; Rect rect; int width; int height; int field_20; // rand int field_24; // rand int field_28; unsigned char* buffer; Button* buttonListHead; Button* field_34; Button* field_38; MenuBar* menuBar; WindowBlitProc* blitProc; } Window; static_assert(sizeof(Window) == 68, "wrong size"); typedef struct Button { int id; int flags; Rect rect; int mouseEnterEventCode; int mouseExitEventCode; int lefMouseDownEventCode; int leftMouseUpEventCode; int rightMouseDownEventCode; int rightMouseUpEventCode; unsigned char* mouseUpImage; unsigned char* mouseDownImage; unsigned char* mouseHoverImage; unsigned char* field_3C; unsigned char* field_40; unsigned char* field_44; unsigned char* currentImage; unsigned char* mask; ButtonCallback* mouseEnterProc; ButtonCallback* mouseExitProc; ButtonCallback* leftMouseDownProc; ButtonCallback* leftMouseUpProc; ButtonCallback* rightMouseDownProc; ButtonCallback* rightMouseUpProc; ButtonCallback* onPressed; ButtonCallback* onUnpressed; RadioGroup* radioGroup; Button* prev; Button* next; } Button; static_assert(sizeof(Button) == 124, "wrong size"); typedef struct RadioGroup { int field_0; int field_4; void (*field_8)(int); int buttonsLength; Button* buttons[RADIO_GROUP_BUTTON_LIST_CAPACITY]; } RadioGroup; #endif /* FALLOUT_PLIB_GNW_GNW_TYPES_H_ */ ================================================ FILE: src/plib/gnw/grbuf.c ================================================ #include "plib/gnw/grbuf.h" #include #include "plib/color/color.h" #include "plib/gnw/input.h" #include "mmx.h" // 0x4D2FC0 void draw_line(unsigned char* buf, int pitch, int x1, int y1, int x2, int y2, int color) { int temp; int dx; int dy; unsigned char* p1; unsigned char* p2; unsigned char* p3; unsigned char* p4; if (x1 == x2) { if (y1 > y2) { temp = y1; y1 = y2; y2 = temp; } p1 = buf + pitch * y1 + x1; p2 = buf + pitch * y2 + x2; while (p1 < p2) { *p1 = color; *p2 = color; p1 += pitch; p2 -= pitch; } } else { if (x1 > x2) { temp = x1; x1 = x2; x2 = temp; temp = y1; y1 = y2; y2 = temp; } p1 = buf + pitch * y1 + x1; p2 = buf + pitch * y2 + x2; if (y1 == y2) { memset(p1, color, p2 - p1); } else { dx = x2 - x1; int v23; int v22; int midX = x1 + (x2 - x1) / 2; if (y1 <= y2) { dy = y2 - y1; v23 = pitch; v22 = midX + ((y2 - y1) / 2 + y1) * pitch; } else { dy = y1 - y2; v23 = -pitch; v22 = midX + (y1 - (y1 - y2) / 2) * pitch; } p3 = buf + v22; p4 = p3; if (dx <= dy) { int v28 = dx - (dy / 2); int v29 = dy / 4; while (true) { *p1 = color; *p2 = color; *p3 = color; *p4 = color; if (v29 == 0) { break; } if (v28 >= 0) { p3++; p2--; p4--; p1++; v28 -= dy; } p3 += v23; p2 -= v23; p4 -= v23; p1 += v23; v28 += dx; v29--; } } else { int v26 = dy - (dx / 2); int v27 = dx / 4; while (true) { *p1 = color; *p2 = color; *p3 = color; *p4 = color; if (v27 == 0) { break; } if (v26 >= 0) { p3 += v23; p2 -= v23; p4 -= v23; p1 += v23; v26 -= dx; } p3++; p2--; p4--; p1++; v26 += dy; v27--; } } } } } // 0x4D31A4 void draw_box(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color) { draw_line(buf, pitch, left, top, right, top, color); draw_line(buf, pitch, left, bottom, right, bottom, color); draw_line(buf, pitch, left, top, left, bottom, color); draw_line(buf, pitch, right, top, right, bottom, color); } // 0x4D322C void draw_shaded_box(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int ltColor, int rbColor) { draw_line(buf, pitch, left, top, right, top, ltColor); draw_line(buf, pitch, left, bottom, right, bottom, rbColor); draw_line(buf, pitch, left, top, left, bottom, ltColor); draw_line(buf, pitch, right, top, right, bottom, rbColor); } // 0x4D33F0 void cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch) { int heightRatio = (destHeight << 16) / srcHeight; int widthRatio = (destWidth << 16) / srcWidth; int v1 = 0; int v2 = heightRatio; for (int srcY = 0; srcY < srcHeight; srcY += 1) { int v3 = widthRatio; int v4 = (heightRatio * srcY) >> 16; int v5 = v2 >> 16; int v6 = 0; unsigned char* c = src + v1; for (int srcX = 0; srcX < srcWidth; srcX += 1) { int v7 = v3 >> 16; int v8 = v6 >> 16; unsigned char* v9 = dest + destPitch * v4 + v8; for (int destY = v4; destY < v5; destY += 1) { for (int destX = v8; destX < v7; destX += 1) { *v9++ = *c; } v9 += destPitch; } v3 += widthRatio; c++; v6 += widthRatio; } v1 += srcPitch; v2 += heightRatio; } } // 0x4D3560 void trans_cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch) { int heightRatio = (destHeight << 16) / srcHeight; int widthRatio = (destWidth << 16) / srcWidth; int v1 = 0; int v2 = heightRatio; for (int srcY = 0; srcY < srcHeight; srcY += 1) { int v3 = widthRatio; int v4 = (heightRatio * srcY) >> 16; int v5 = v2 >> 16; int v6 = 0; unsigned char* c = src + v1; for (int srcX = 0; srcX < srcWidth; srcX += 1) { int v7 = v3 >> 16; int v8 = v6 >> 16; if (*c != 0) { unsigned char* v9 = dest + destPitch * v4 + v8; for (int destY = v4; destY < v5; destY += 1) { for (int destX = v8; destX < v7; destX += 1) { *v9++ = *c; } v9 += destPitch; } } v3 += widthRatio; c++; v6 += widthRatio; } v1 += srcPitch; v2 += heightRatio; } } // 0x4D36D4 void buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch) { mmxBlit(dest, destPitch, src, srcPitch, width, height); } // 0x4D3704 void trans_buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch) { mmxBlitTrans(dest, destPitch, src, srcPitch, width, height); } // 0x4D387C void buf_fill(unsigned char* buf, int width, int height, int pitch, int a5) { int y; for (y = 0; y < height; y++) { memset(buf, a5, width); buf += pitch; } } // 0x4D38E0 void buf_texture(unsigned char* buf, int width, int height, int pitch, void* a5, int a6, int a7) { // TODO: Incomplete. } // 0x4D3A48 void lighten_buf(unsigned char* buf, int width, int height, int pitch) { int skip = pitch - width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { unsigned char p = *buf; *buf++ = intensityColorTable[p][147]; } buf += skip; } } // Swaps two colors in the buffer. // // 0x4D3A8C void swap_color_buf(unsigned char* buf, int width, int height, int pitch, int color1, int color2) { int step = pitch - width; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int v1 = *buf & 0xFF; if (v1 == color1) { *buf = color2 & 0xFF; } else if (v1 == color2) { *buf = color1 & 0xFF; } buf++; } buf += step; } } // 0x4D3AE0 void buf_outline(unsigned char* buf, int width, int height, int pitch, int color) { unsigned char* ptr = buf + pitch; bool cycle; for (int y = 0; y < height - 2; y++) { cycle = true; for (int x = 0; x < width; x++) { if (*ptr != 0 && cycle) { *(ptr - 1) = color & 0xFF; cycle = false; } else if (*ptr == 0 && !cycle) { *ptr = color & 0xFF; cycle = true; } ptr++; } ptr += pitch - width; } for (int x = 0; x < width; x++) { ptr = buf + x; cycle = true; for (int y = 0; y < height; y++) { if (*ptr != 0 && cycle) { // TODO: Check in debugger, might be a bug. *(ptr - pitch) = color & 0xFF; cycle = false; } else if (*ptr == 0 && !cycle) { *ptr = color & 0xFF; cycle = true; } ptr += pitch; } } } ================================================ FILE: src/plib/gnw/grbuf.h ================================================ #ifndef FALLOUT_PLIB_GNW_GRBUF_H_ #define FALLOUT_PLIB_GNW_GRBUF_H_ void draw_line(unsigned char* buf, int pitch, int left, int top, int right, int bottom, int color); void draw_box(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7); void draw_shaded_box(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7, int a8); void cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch); void trans_cscale(unsigned char* src, int srcWidth, int srcHeight, int srcPitch, unsigned char* dest, int destWidth, int destHeight, int destPitch); void buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch); void trans_buf_to_buf(unsigned char* src, int width, int height, int srcPitch, unsigned char* dest, int destPitch); void buf_fill(unsigned char* buf, int width, int height, int pitch, int a5); void buf_texture(unsigned char* buf, int width, int height, int pitch, void* a5, int a6, int a7); void lighten_buf(unsigned char* buf, int width, int height, int pitch); void swap_color_buf(unsigned char* buf, int width, int height, int pitch, int color1, int color2); void buf_outline(unsigned char* buf, int width, int height, int pitch, int a5); #endif /* FALLOUT_PLIB_GNW_GRBUF_H_ */ ================================================ FILE: src/plib/gnw/input.c ================================================ #include "plib/gnw/input.h" #include "plib/color/color.h" #include "plib/gnw/button.h" #include "plib/gnw/dxinput.h" #include "plib/gnw/grbuf.h" #include "plib/gnw/memory.h" #include "mmx.h" #include "plib/gnw/text.h" #include "plib/gnw/vcr.h" #include "plib/gnw/gnw.h" #include "plib/gnw/intrface.h" #include "plib/gnw/svga.h" #include "plib/gnw/winmain.h" typedef struct GNW95RepeatStruct { // Time when appropriate key was pressed down or -1 if it's up. TOCKS time; unsigned short count; } GNW95RepeatStruct; typedef struct inputdata { // This is either logical key or input event id, which can be either // character code pressed or some other numbers used throughout the // game interface. int input; int mx; int my; } inputdata; typedef struct funcdata { unsigned int flags; BackgroundProcess* f; struct funcdata* next; } funcdata; typedef funcdata* FuncPtr; static int get_input_buffer(); static void pause_game(); static int default_pause_window(); static void buf_blit(unsigned char* src, int src_pitch, int a3, int x, int y, int width, int height, int dest_x, int dest_y); static void GNW95_build_key_map(); static int GNW95_hook_keyboard(int hook); static void GNW95_process_key(dxinput_key_data* data); // NOT USED. static IdleFunc* idle_func = NULL; // NOT USED. static FocusFunc* focus_func = NULL; // 0x51E23C static int GNW95_repeat_rate = 80; // 0x51E240 static int GNW95_repeat_delay = 500; // A map of DIK_* constants normalized for QWERTY keyboard. // // 0x6ABC70 unsigned char GNW95_key_map[256]; // Ring buffer of input events. // // Looks like this buffer does not support overwriting of values. Once the // buffer is full it will not overwrite values until they are dequeued. // // 0x6ABD70 static inputdata input_buffer[40]; // 0x6ABF50 GNW95RepeatStruct GNW95_key_time_stamps[256]; // 0x6AC750 static int input_mx; // 0x6AC754 static int input_my; // 0x6AC758 static HHOOK GNW95_keyboardHandle; // 0x6AC75C static bool game_paused; // 0x6AC760 static int screendump_key; // 0x6AC764 static int using_msec_timer; // 0x6AC768 static int pause_key; // 0x6AC76C static ScreenDumpFunc* screendump_func; // 0x6AC770 static int input_get; // 0x6AC774 static unsigned char* screendump_buf; // 0x6AC778 static PauseWinFunc* pause_win_func; // 0x6AC77C static int input_put; // 0x6AC780 static bool bk_disabled; // 0x6AC784 static FuncPtr bk_list; // 0x6AC788 static unsigned int bk_process_time; // 0x4C8A70 int GNW_input_init(int use_msec_timer) { if (!dxinput_init()) { return -1; } if (GNW_kb_set() == -1) { return -1; } if (GNW_mouse_init() == -1) { return -1; } if (GNW95_input_init() == -1) { return -1; } GNW95_hook_input(1); GNW95_build_key_map(); GNW95_clear_time_stamps(); using_msec_timer = use_msec_timer; input_put = 0; input_get = -1; input_mx = -1; input_my = -1; bk_disabled = 0; game_paused = false; pause_key = KEY_ALT_P; pause_win_func = default_pause_window; screendump_func = default_screendump; bk_list = NULL; screendump_key = KEY_ALT_C; return 0; } // 0x4C8B40 void GNW_input_exit() { // NOTE: Uninline. GNW95_input_exit(); GNW_mouse_exit(); GNW_kb_restore(); dxinput_exit(); FuncPtr curr = bk_list; while (curr != NULL) { FuncPtr next = curr->next; mem_free(curr); curr = next; } } // 0x4C8B78 int get_input() { int v3; GNW95_process_message(); if (!GNW95_isActive) { GNW95_lost_focus(); } process_bk(); v3 = get_input_buffer(); if (v3 == -1 && mouse_get_buttons() & 0x33) { mouse_get_position(&input_mx, &input_my); return -2; } else { return GNW_check_menu_bars(v3); } return -1; } // 0x4C8BDC void process_bk() { int v1; GNW_do_bk_process(); if (vcr_update() != 3) { mouse_info(); } v1 = win_check_all_buttons(); if (v1 != -1) { GNW_add_input_buffer(v1); return; } v1 = kb_getch(); if (v1 != -1) { GNW_add_input_buffer(v1); return; } } // 0x4C8C04 void GNW_add_input_buffer(int a1) { if (a1 == -1) { return; } if (a1 == pause_key) { pause_game(); return; } if (a1 == screendump_key) { dump_screen(); return; } if (input_put == input_get) { return; } inputdata* inputEvent = &(input_buffer[input_put]); inputEvent->input = a1; mouse_get_position(&(inputEvent->mx), &(inputEvent->my)); input_put++; if (input_put == 40) { input_put = 0; return; } if (input_get == -1) { input_get = 0; } } // 0x4C8C9C static int get_input_buffer() { if (input_get == -1) { return -1; } inputdata* inputEvent = &(input_buffer[input_get]); int eventCode = inputEvent->input; input_mx = inputEvent->mx; input_my = inputEvent->my; input_get++; if (input_get == 40) { input_get = 0; } if (input_get == input_put) { input_get = -1; input_put = 0; } return eventCode; } // 0x4C8D04 void flush_input_buffer() { input_get = -1; input_put = 0; } // 0x4C8D1C void GNW_do_bk_process() { if (game_paused) { return; } if (bk_disabled) { return; } bk_process_time = get_time(); FuncPtr curr = bk_list; FuncPtr* currPtr = &(bk_list); while (curr != NULL) { FuncPtr next = curr->next; if (curr->flags & 1) { *currPtr = next; mem_free(curr); } else { curr->f(); currPtr = &(curr->next); } curr = next; } } // 0x4C8D74 void add_bk_process(BackgroundProcess* f) { FuncPtr fp; fp = bk_list; while (fp != NULL) { if (fp->f == f) { if ((fp->flags & 0x01) != 0) { fp->flags &= ~0x01; return; } } fp = fp->next; } fp = (FuncPtr)mem_malloc(sizeof(*fp)); fp->flags = 0; fp->f = f; fp->next = bk_list; bk_list = fp; } // 0x4C8DC4 void remove_bk_process(BackgroundProcess* f) { FuncPtr fp; fp = bk_list; while (fp != NULL) { if (fp->f == f) { fp->flags |= 0x01; return; } fp = fp->next; } } // 0x4C8DE4 void enable_bk() { bk_disabled = false; } // 0x4C8DF0 void disable_bk() { bk_disabled = true; } // 0x4C8DFC static void pause_game() { if (!game_paused) { game_paused = true; int win = pause_win_func(); while (get_input() != KEY_ESCAPE) { } game_paused = false; win_delete(win); } } // 0x4C8E38 static int default_pause_window() { int windowWidth = text_width("Paused") + 32; int windowHeight = 3 * text_height() + 16; int win = win_add((rectGetWidth(&scr_size) - windowWidth) / 2, (rectGetHeight(&scr_size) - windowHeight) / 2, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { return -1; } win_border(win); unsigned char* windowBuffer = win_get_buf(win); text_to_buf(windowBuffer + 8 * windowWidth + 16, "Paused", windowWidth, windowWidth, colorTable[31744]); win_register_text_button(win, (windowWidth - text_width("Done") - 16) / 2, windowHeight - 8 - text_height() - 6, -1, -1, -1, KEY_ESCAPE, "Done", 0); win_draw(win); return win; } // 0x4C8F34 void register_pause(int new_pause_key, PauseWinFunc* new_pause_win_func) { pause_key = new_pause_key; if (new_pause_win_func == NULL) { new_pause_win_func = default_pause_window; } pause_win_func = new_pause_win_func; } // 0x4C8F4C void dump_screen() { int width = scr_size.lrx - scr_size.ulx + 1; int height = scr_size.lry - scr_size.uly + 1; screendump_buf = (unsigned char*)mem_malloc(width * height); if (screendump_buf == NULL) { return; } ScreenBlitFunc* v0 = scr_blit; scr_blit = buf_blit; ScreenBlitFunc* v2 = mouse_blit; mouse_blit = buf_blit; ScreenTransBlitFunc* v1 = mouse_blit_trans; mouse_blit_trans = NULL; win_refresh_all(&scr_size); mouse_blit_trans = v1; mouse_blit = v2; scr_blit = v0; unsigned char* palette = getSystemPalette(); screendump_func(width, height, screendump_buf, palette); mem_free(screendump_buf); } // 0x4C8FF0 static void buf_blit(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int width, int height, int destX, int destY) { int destWidth = scr_size.lrx - scr_size.ulx + 1; buf_to_buf(src + srcPitch * srcY + srcX, width, height, srcPitch, screendump_buf + destWidth * destY + destX, destWidth); } // 0x4C9048 int default_screendump(int width, int height, unsigned char* data, unsigned char* palette) { char fileName[16]; FILE* stream; int index; unsigned int intValue; unsigned short shortValue; for (index = 0; index < 100000; index++) { sprintf(fileName, "scr%.5d.bmp", index); stream = fopen(fileName, "rb"); if (stream == NULL) { break; } fclose(stream); } if (index == 100000) { return -1; } stream = fopen(fileName, "wb"); if (stream == NULL) { return -1; } // bfType shortValue = 0x4D42; fwrite(&shortValue, sizeof(shortValue), 1, stream); // bfSize // 14 - sizeof(BITMAPFILEHEADER) // 40 - sizeof(BITMAPINFOHEADER) // 1024 - sizeof(RGBQUAD) * 256 intValue = width * height + 14 + 40 + 1024; fwrite(&intValue, sizeof(intValue), 1, stream); // bfReserved1 shortValue = 0; fwrite(&shortValue, sizeof(shortValue), 1, stream); // bfReserved2 shortValue = 0; fwrite(&shortValue, sizeof(shortValue), 1, stream); // bfOffBits intValue = 14 + 40 + 1024; fwrite(&intValue, sizeof(intValue), 1, stream); // biSize intValue = 40; fwrite(&intValue, sizeof(intValue), 1, stream); // biWidth intValue = width; fwrite(&intValue, sizeof(intValue), 1, stream); // biHeight intValue = height; fwrite(&intValue, sizeof(intValue), 1, stream); // biPlanes shortValue = 1; fwrite(&shortValue, sizeof(shortValue), 1, stream); // biBitCount shortValue = 8; fwrite(&shortValue, sizeof(shortValue), 1, stream); // biCompression intValue = 0; fwrite(&intValue, sizeof(intValue), 1, stream); // biSizeImage intValue = 0; fwrite(&intValue, sizeof(intValue), 1, stream); // biXPelsPerMeter intValue = 0; fwrite(&intValue, sizeof(intValue), 1, stream); // biYPelsPerMeter intValue = 0; fwrite(&intValue, sizeof(intValue), 1, stream); // biClrUsed intValue = 0; fwrite(&intValue, sizeof(intValue), 1, stream); // biClrImportant intValue = 0; fwrite(&intValue, sizeof(intValue), 1, stream); for (int index = 0; index < 256; index++) { unsigned char rgbReserved = 0; unsigned char rgbRed = palette[index * 3] << 2; unsigned char rgbGreen = palette[index * 3 + 1] << 2; unsigned char rgbBlue = palette[index * 3 + 2] << 2; fwrite(&rgbBlue, sizeof(rgbBlue), 1, stream); fwrite(&rgbGreen, sizeof(rgbGreen), 1, stream); fwrite(&rgbRed, sizeof(rgbRed), 1, stream); fwrite(&rgbReserved, sizeof(rgbReserved), 1, stream); } for (int y = height - 1; y >= 0; y--) { unsigned char* dataPtr = data + y * width; fwrite(dataPtr, 1, width, stream); } fflush(stream); fclose(stream); return 0; } // 0x4C9358 void register_screendump(int new_screendump_key, ScreenDumpFunc* new_screendump_func) { screendump_key = new_screendump_key; if (new_screendump_func == NULL) { new_screendump_func = default_screendump; } screendump_func = new_screendump_func; } // 0x4C9370 TOCKS get_time() { #pragma warning(suppress : 28159) return GetTickCount(); } // 0x4C937C void pause_for_tocks(unsigned int delay) { // NOTE: Uninline. unsigned int start = get_time(); unsigned int end = get_time(); // NOTE: Uninline. unsigned int diff = elapsed_tocks(end, start); while (diff < delay) { process_bk(); end = get_time(); // NOTE: Uninline. diff = elapsed_tocks(end, start); } } // 0x4C93B8 void block_for_tocks(unsigned int ms) { #pragma warning(suppress : 28159) unsigned int start = GetTickCount(); unsigned int diff; do { // NOTE: Uninline diff = elapsed_time(start); } while (diff < ms); } // 0x4C93E0 unsigned int elapsed_time(unsigned int start) { #pragma warning(suppress : 28159) unsigned int end = GetTickCount(); // NOTE: Uninline. return elapsed_tocks(end, start); } // 0x4C9400 unsigned int elapsed_tocks(unsigned int end, unsigned int start) { if (start > end) { return INT_MAX; } else { return end - start; } } // 0x4C9410 unsigned int get_bk_time() { return bk_process_time; } // 0x4C9490 static void GNW95_build_key_map() { unsigned char* keys = GNW95_key_map; int k; keys[DIK_ESCAPE] = DIK_ESCAPE; keys[DIK_1] = DIK_1; keys[DIK_2] = DIK_2; keys[DIK_3] = DIK_3; keys[DIK_4] = DIK_4; keys[DIK_5] = DIK_5; keys[DIK_6] = DIK_6; keys[DIK_7] = DIK_7; keys[DIK_8] = DIK_8; keys[DIK_9] = DIK_9; keys[DIK_0] = DIK_0; switch (kb_layout) { case 0: k = DIK_MINUS; break; case 1: k = DIK_6; break; default: k = DIK_SLASH; break; } keys[DIK_MINUS] = k; switch (kb_layout) { case 1: k = DIK_0; break; default: k = DIK_EQUALS; break; } keys[DIK_EQUALS] = k; keys[DIK_BACK] = DIK_BACK; keys[DIK_TAB] = DIK_TAB; switch (kb_layout) { case 1: k = DIK_A; break; default: k = DIK_Q; break; } keys[DIK_Q] = k; switch (kb_layout) { case 1: k = DIK_Z; break; default: k = DIK_W; break; } keys[DIK_W] = k; keys[DIK_E] = DIK_E; keys[DIK_R] = DIK_R; keys[DIK_T] = DIK_T; switch (kb_layout) { case 0: case 1: case 3: case 4: k = DIK_Y; break; default: k = DIK_Z; break; } keys[DIK_Y] = k; keys[DIK_U] = DIK_U; keys[DIK_I] = DIK_I; keys[DIK_O] = DIK_O; keys[DIK_P] = DIK_P; switch (kb_layout) { case 0: case 3: case 4: k = DIK_LBRACKET; break; case 1: k = DIK_5; break; default: k = DIK_8; break; } keys[DIK_LBRACKET] = k; switch (kb_layout) { case 0: case 3: case 4: k = DIK_RBRACKET; break; case 1: k = DIK_MINUS; break; default: k = DIK_9; break; } keys[DIK_RBRACKET] = k; keys[DIK_RETURN] = DIK_RETURN; keys[DIK_LCONTROL] = DIK_LCONTROL; switch (kb_layout) { case 1: k = DIK_Q; break; default: k = DIK_A; break; } keys[DIK_A] = k; keys[DIK_S] = DIK_S; keys[DIK_D] = DIK_D; keys[DIK_F] = DIK_F; keys[DIK_G] = DIK_G; keys[DIK_H] = DIK_H; keys[DIK_J] = DIK_J; keys[DIK_K] = DIK_K; keys[DIK_L] = DIK_L; switch (kb_layout) { case 0: k = DIK_SEMICOLON; break; default: k = DIK_COMMA; break; } keys[DIK_SEMICOLON] = k; switch (kb_layout) { case 0: k = DIK_APOSTROPHE; break; case 1: k = DIK_4; break; default: k = DIK_MINUS; break; } keys[DIK_APOSTROPHE] = k; switch (kb_layout) { case 0: k = DIK_GRAVE; break; case 1: k = DIK_2; break; case 3: case 4: k = 0; break; default: k = DIK_RBRACKET; break; } keys[DIK_GRAVE] = k; keys[DIK_LSHIFT] = DIK_LSHIFT; switch (kb_layout) { case 0: k = DIK_BACKSLASH; break; case 1: k = DIK_8; break; case 3: case 4: k = DIK_GRAVE; break; default: k = DIK_Y; break; } keys[DIK_BACKSLASH] = k; switch (kb_layout) { case 0: case 3: case 4: k = DIK_Z; break; case 1: k = DIK_W; break; default: k = DIK_Y; break; } keys[DIK_Z] = k; keys[DIK_X] = DIK_X; keys[DIK_C] = DIK_C; keys[DIK_V] = DIK_V; keys[DIK_B] = DIK_B; keys[DIK_N] = DIK_N; switch (kb_layout) { case 1: k = DIK_SEMICOLON; break; default: k = DIK_M; break; } keys[DIK_M] = k; switch (kb_layout) { case 1: k = DIK_M; break; default: k = DIK_COMMA; break; } keys[DIK_COMMA] = k; switch (kb_layout) { case 1: k = DIK_COMMA; break; default: k = DIK_PERIOD; break; } keys[DIK_PERIOD] = k; switch (kb_layout) { case 0: k = DIK_SLASH; break; case 1: k = DIK_PERIOD; break; default: k = DIK_7; break; } keys[DIK_SLASH] = k; keys[DIK_RSHIFT] = DIK_RSHIFT; keys[DIK_MULTIPLY] = DIK_MULTIPLY; keys[DIK_SPACE] = DIK_SPACE; keys[DIK_LMENU] = DIK_LMENU; keys[DIK_CAPITAL] = DIK_CAPITAL; keys[DIK_F1] = DIK_F1; keys[DIK_F2] = DIK_F2; keys[DIK_F3] = DIK_F3; keys[DIK_F4] = DIK_F4; keys[DIK_F5] = DIK_F5; keys[DIK_F6] = DIK_F6; keys[DIK_F7] = DIK_F7; keys[DIK_F8] = DIK_F8; keys[DIK_F9] = DIK_F9; keys[DIK_F10] = DIK_F10; keys[DIK_NUMLOCK] = DIK_NUMLOCK; keys[DIK_SCROLL] = DIK_SCROLL; keys[DIK_NUMPAD7] = DIK_NUMPAD7; keys[DIK_NUMPAD9] = DIK_NUMPAD9; keys[DIK_NUMPAD8] = DIK_NUMPAD8; keys[DIK_SUBTRACT] = DIK_SUBTRACT; keys[DIK_NUMPAD4] = DIK_NUMPAD4; keys[DIK_NUMPAD5] = DIK_NUMPAD5; keys[DIK_NUMPAD6] = DIK_NUMPAD6; keys[DIK_ADD] = DIK_ADD; keys[DIK_NUMPAD1] = DIK_NUMPAD1; keys[DIK_NUMPAD2] = DIK_NUMPAD2; keys[DIK_NUMPAD3] = DIK_NUMPAD3; keys[DIK_NUMPAD0] = DIK_NUMPAD0; keys[DIK_DECIMAL] = DIK_DECIMAL; keys[DIK_F11] = DIK_F11; keys[DIK_F12] = DIK_F12; keys[DIK_F13] = -1; keys[DIK_F14] = -1; keys[DIK_F15] = -1; keys[DIK_KANA] = -1; keys[DIK_CONVERT] = -1; keys[DIK_NOCONVERT] = -1; keys[DIK_YEN] = -1; keys[DIK_NUMPADEQUALS] = -1; keys[DIK_PREVTRACK] = -1; keys[DIK_AT] = -1; keys[DIK_COLON] = -1; keys[DIK_UNDERLINE] = -1; keys[DIK_KANJI] = -1; keys[DIK_STOP] = -1; keys[DIK_AX] = -1; keys[DIK_UNLABELED] = -1; keys[DIK_NUMPADENTER] = DIK_NUMPADENTER; keys[DIK_RCONTROL] = DIK_RCONTROL; keys[DIK_NUMPADCOMMA] = -1; keys[DIK_DIVIDE] = DIK_DIVIDE; keys[DIK_SYSRQ] = 84; keys[DIK_RMENU] = DIK_RMENU; keys[DIK_HOME] = DIK_HOME; keys[DIK_UP] = DIK_UP; keys[DIK_PRIOR] = DIK_PRIOR; keys[DIK_LEFT] = DIK_LEFT; keys[DIK_RIGHT] = DIK_RIGHT; keys[DIK_END] = DIK_END; keys[DIK_DOWN] = DIK_DOWN; keys[DIK_NEXT] = DIK_NEXT; keys[DIK_INSERT] = DIK_INSERT; keys[DIK_DELETE] = DIK_DELETE; keys[DIK_LWIN] = -1; keys[DIK_RWIN] = -1; keys[DIK_APPS] = -1; } // 0x4C9BB4 void GNW95_hook_input(int hook) { GNW95_hook_keyboard(hook); if (hook) { dxinput_acquire_mouse(); } else { dxinput_unacquire_mouse(); } } // 0x4C9C20 int GNW95_input_init() { return 0; } // NOTE: Inlined. // // 0x4C9C24 void GNW95_input_exit() { GNW95_hook_keyboard(0); } // 0x4C9C28 static int GNW95_hook_keyboard(int hook) { // 0x51E244 static bool hooked = false; if (hook == hooked) { return 0; } if (!hook) { dxinput_unacquire_keyboard(); UnhookWindowsHookEx(GNW95_keyboardHandle); kb_clear(); hooked = hook; return 0; } if (dxinput_acquire_keyboard()) { GNW95_keyboardHandle = SetWindowsHookExA(WH_KEYBOARD, GNW95_keyboard_hook, 0, GetCurrentThreadId()); kb_clear(); hooked = hook; return 0; } return -1; } // 0x4C9C4C LRESULT CALLBACK GNW95_keyboard_hook(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0) { if (wParam == VK_DELETE && lParam & 0x20000000 && GetAsyncKeyState(VK_CONTROL) & 0x80000000) return 0; if (wParam == VK_ESCAPE && GetAsyncKeyState(VK_CONTROL) & 0x80000000) return 0; if (wParam == VK_RETURN && lParam & 0x20000000) return 0; if (wParam == VK_NUMLOCK || wParam == VK_CAPITAL || wParam == VK_SCROLL) { // TODO: Get rid of this goto. goto next; } return 1; } next: return CallNextHookEx(GNW95_keyboardHandle, nCode, wParam, lParam); } // 0x4C9CF0 void GNW95_process_message() { if (GNW95_isActive && !kb_is_disabled()) { dxinput_key_data data; while (dxinput_read_keyboard_buffer(&data)) { GNW95_process_key(&data); } // NOTE: Uninline TOCKS now = get_time(); for (int key = 0; key < 256; key++) { GNW95RepeatStruct* ptr = &(GNW95_key_time_stamps[key]); if (ptr->time != -1) { int elapsedTime = ptr->time > now ? INT_MAX : now - ptr->time; int delay = ptr->count == 0 ? GNW95_repeat_delay : GNW95_repeat_rate; if (elapsedTime > delay) { data.code = key; data.state = 1; GNW95_process_key(&data); ptr->time = now; ptr->count++; } } } } MSG msg; while (PeekMessageA(&msg, NULL, 0, 0, 0)) { if (GetMessageA(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); } } } // 0x4C9DF0 void GNW95_clear_time_stamps() { for (int index = 0; index < 256; index++) { GNW95_key_time_stamps[index].time = -1; GNW95_key_time_stamps[index].count = 0; } } // 0x4C9E14 static void GNW95_process_key(dxinput_key_data* data) { short key = data->code & 0xFF; switch (key) { case DIK_NUMPADENTER: case DIK_RCONTROL: case DIK_DIVIDE: case DIK_RMENU: case DIK_HOME: case DIK_UP: case DIK_PRIOR: case DIK_LEFT: case DIK_RIGHT: case DIK_END: case DIK_DOWN: case DIK_NEXT: case DIK_INSERT: case DIK_DELETE: key |= 0x0100; break; } int qwertyKey = GNW95_key_map[data->code & 0xFF]; if (vcr_state == VCR_STATE_PLAYING) { if ((vcr_terminate_flags & VCR_TERMINATE_ON_KEY_PRESS) != 0) { vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_TERMINATED; vcr_stop(); } } else { if ((key & 0x0100) != 0) { kb_simulate_key(224); qwertyKey -= 0x80; } GNW95RepeatStruct* ptr = &(GNW95_key_time_stamps[data->code & 0xFF]); if (data->state == 1) { ptr->time = get_time(); ptr->count = 0; } else { qwertyKey |= 0x80; ptr->time = -1; } kb_simulate_key(qwertyKey); } } // 0x4C9EEC void GNW95_lost_focus() { if (focus_func != NULL) { focus_func(0); } while (!GNW95_isActive) { GNW95_process_message(); if (idle_func != NULL) { idle_func(); } } if (focus_func != NULL) { focus_func(1); } } ================================================ FILE: src/plib/gnw/input.h ================================================ #ifndef FALLOUT_PLIB_GNW_INPUT_H_ #define FALLOUT_PLIB_GNW_INPUT_H_ #include #define WIN32_LEAN_AND_MEAN #include #include "plib/gnw/kb.h" #include "plib/gnw/mouse.h" typedef unsigned long TOCKS; typedef void(IdleFunc)(); typedef void(FocusFunc)(int); typedef void(BackgroundProcess)(); typedef int(PauseWinFunc)(); typedef int(ScreenDumpFunc)(int width, int height, unsigned char* buffer, unsigned char* palette); int GNW_input_init(int use_msec_timer); void GNW_input_exit(); int get_input(); void process_bk(); void GNW_add_input_buffer(int a1); void flush_input_buffer(); void GNW_do_bk_process(); void add_bk_process(BackgroundProcess* f); void remove_bk_process(BackgroundProcess* f); void enable_bk(); void disable_bk(); void register_pause(int new_pause_key, PauseWinFunc* new_pause_win_func); void dump_screen(); int default_screendump(int width, int height, unsigned char* data, unsigned char* palette); void register_screendump(int new_screendump_key, ScreenDumpFunc* new_screendump_func); TOCKS get_time(); void pause_for_tocks(unsigned int ms); void block_for_tocks(unsigned int ms); unsigned int elapsed_time(unsigned int a1); unsigned int elapsed_tocks(unsigned int a1, unsigned int a2); unsigned int get_bk_time(); void GNW95_hook_input(int hook); int GNW95_input_init(); void GNW95_input_exit(); LRESULT CALLBACK GNW95_keyboard_hook(int nCode, WPARAM wParam, LPARAM lParam); void GNW95_process_message(); void GNW95_clear_time_stamps(); void GNW95_lost_focus(); #endif /* FALLOUT_PLIB_GNW_INPUT_H_ */ ================================================ FILE: src/plib/gnw/intrface.c ================================================ #include "plib/gnw/intrface.h" #include #include #include "plib/color/color.h" #include "plib/gnw/input.h" #include "plib/gnw/grbuf.h" #include "plib/gnw/button.h" #include "plib/gnw/memory.h" #include "plib/gnw/text.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" static int create_pull_down(char** stringList, int stringListLength, int x, int y, int a5, int a6, Rect* rect); static void win_debug_delete(int btn, int keyCode); static int find_first_letter(int ch, char** stringList, int stringListLength); static int process_pull_down(int win, Rect* rect, char** items, int itemsLength, int a5, int a6, MenuBar* menuBar, int pulldownIndex); static int calc_max_field_chars_wcursor(int a1, int a2); static void tm_watch_msgs(); static void tm_kill_msg(); static void tm_kill_out_of_order(int a1); static void tm_click_response(int btn); static int tm_index_active(int a1); // 0x51E414 static int wd = -1; // 0x51E41C static bool tm_watch_active = false; // 0x6B2340 static struct { int taken; int y; } tm_location[5]; // 0x6B2368 static int tm_text_x; // 0x6B236C static int tm_h; // 0x6B2370 static struct { int created; int id; int location; } tm_queue[5]; // 0x6B23AC static unsigned int tm_persistence; // 0x6B23B0 static int scr_center_x; // 0x6B23B4 int tm_text_y; // 0x6B23B8 int tm_kill; // 0x6B23BC int tm_add; // 0x6B23C0 int curry; // 0x6B23C4 int currx; // 0x4DA6C0 int win_list_select(const char* title, char** fileList, int fileListLength, SelectFunc* callback, int x, int y, int a7) { return win_list_select_at(title, fileList, fileListLength, callback, x, y, a7, 0); } // 0x4DA70C int win_list_select_at(const char* title, char** items, int itemsLength, SelectFunc* callback, int x, int y, int a7, int a8) { if (!GNW_win_init_flag) { return -1; } int listViewWidth = win_width_needed(items, itemsLength); int windowWidth = listViewWidth + 16; int titleWidth = text_width(title); if (titleWidth > windowWidth) { windowWidth = titleWidth; listViewWidth = titleWidth - 16; } windowWidth += 20; int win; int windowHeight; int listViewCapacity = 10; for (int heightMultiplier = 13; heightMultiplier > 8; heightMultiplier--) { windowHeight = heightMultiplier * text_height() + 22; win = win_add(x, y, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win != -1) { break; } listViewCapacity--; } if (win == -1) { return -1; } Window* window = GNW_find(win); Rect* windowRect = &(window->rect); unsigned char* windowBuffer = window->buffer; draw_box(windowBuffer, windowWidth, 0, 0, windowWidth - 1, windowHeight - 1, colorTable[0]); draw_shaded_box(windowBuffer, windowWidth, 1, 1, windowWidth - 2, windowHeight - 2, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]); buf_fill(windowBuffer + windowWidth * 5 + 5, windowWidth - 11, text_height() + 3, windowWidth, colorTable[GNW_wcolor[0]]); text_to_buf(windowBuffer + windowWidth / 2 + 8 * windowWidth - text_width(title) / 2, title, windowWidth, windowWidth, colorTable[GNW_wcolor[3]]); draw_shaded_box(windowBuffer, windowWidth, 5, 5, windowWidth - 6, text_height() + 8, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); int listViewX = 8; int listViewY = text_height() + 16; unsigned char* listViewBuffer = windowBuffer + windowWidth * listViewY + listViewX; int listViewMaxY = listViewCapacity * text_height() + listViewY; buf_fill(listViewBuffer + windowWidth * (-2) + (-3), listViewWidth + listViewX - 2, listViewCapacity * text_height() + 2, windowWidth, colorTable[GNW_wcolor[0]]); int scrollOffset = a8; if (a8 < 0 || a8 >= itemsLength) { scrollOffset = 0; } // Relative to `scrollOffset`. int selectedItemIndex; if (itemsLength - scrollOffset < listViewCapacity) { int newScrollOffset = itemsLength - listViewCapacity; if (newScrollOffset < 0) { newScrollOffset = 0; } int oldScrollOffset = scrollOffset; scrollOffset = newScrollOffset; selectedItemIndex = oldScrollOffset - newScrollOffset; } else { selectedItemIndex = 0; } char** itemsTO = items + a8; win_text(win, items + a8, itemsLength < listViewCapacity ? itemsLength : listViewCapacity, listViewWidth, listViewX, listViewY, a7 | 0x2000000); lighten_buf(listViewBuffer + windowWidth * selectedItemIndex * text_height(), listViewWidth, text_height(), windowWidth); draw_shaded_box(windowBuffer, windowWidth, 5, listViewY - 3, listViewWidth + 10, listViewMaxY, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); win_register_text_button(win, windowWidth - 25, listViewY - 3, -1, -1, KEY_ARROW_UP, -1, "\x18", 0); win_register_text_button(win, windowWidth - 25, listViewMaxY - text_height() - 5, -1, -1, KEY_ARROW_DOWN, -1, "\x19", 0); win_register_text_button(win, windowWidth / 2 - 32, windowHeight - 8 - text_height() - 6, -1, -1, -1, KEY_ESCAPE, "Done", 0); int scrollbarX = windowWidth - 21; int scrollbarY = listViewY + text_height() + 7; int scrollbarKnobSize = 14; int scrollbarHeight = listViewMaxY - scrollbarY; unsigned char* scrollbarBuffer = windowBuffer + windowWidth * scrollbarY + scrollbarX; buf_fill(scrollbarBuffer, scrollbarKnobSize + 1, scrollbarHeight - text_height() - 8, windowWidth, colorTable[GNW_wcolor[0]]); win_register_button(win, scrollbarX, scrollbarY, scrollbarKnobSize + 1, scrollbarHeight - text_height() - 8, -1, -1, 2048, -1, NULL, NULL, NULL, 0); draw_shaded_box(windowBuffer, windowWidth, windowWidth - 22, scrollbarY - 1, scrollbarX + scrollbarKnobSize + 1, listViewMaxY - text_height() - 9, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); draw_shaded_box(windowBuffer, windowWidth, scrollbarX, scrollbarY, scrollbarX + scrollbarKnobSize, scrollbarY + scrollbarKnobSize, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]); lighten_buf(scrollbarBuffer, scrollbarKnobSize, scrollbarKnobSize, windowWidth); for (int index = 0; index < listViewCapacity; index++) { win_register_button(win, listViewX, listViewY + index * text_height(), listViewWidth, text_height(), 512 + index, -1, 1024 + index, -1, NULL, NULL, NULL, 0); } win_register_button(win, 0, 0, windowWidth, text_height() + 8, -1, -1, -1, -1, NULL, NULL, NULL, BUTTON_FLAG_0x10); win_draw(win); int absoluteSelectedItemIndex = -1; // Relative to `scrollOffset`. int previousSelectedItemIndex = -1; while (1) { int keyCode = get_input(); int mouseX; int mouseY; mouse_get_position(&mouseX, &mouseY); if (keyCode == KEY_RETURN || (keyCode >= 1024 && keyCode < listViewCapacity + 1024)) { if (selectedItemIndex != -1) { absoluteSelectedItemIndex = scrollOffset + selectedItemIndex; if (absoluteSelectedItemIndex < itemsLength) { if (callback == NULL) { break; } callback(items, absoluteSelectedItemIndex); } absoluteSelectedItemIndex = -1; } } else if (keyCode == 2048) { if (window->rect.uly + scrollbarY > mouseY) { keyCode = KEY_PAGE_UP; } else if (window->rect.uly + scrollbarKnobSize + scrollbarY < mouseY) { keyCode = KEY_PAGE_DOWN; } } if (keyCode == KEY_ESCAPE) { break; } if (keyCode >= 512 && keyCode < listViewCapacity + 512) { int itemIndex = keyCode - 512; if (itemIndex != selectedItemIndex && itemIndex < itemsLength) { previousSelectedItemIndex = selectedItemIndex; selectedItemIndex = itemIndex; keyCode = -3; } else { continue; } } else { switch (keyCode) { case KEY_HOME: if (scrollOffset > 0) { keyCode = -4; scrollOffset = 0; } break; case KEY_ARROW_UP: if (selectedItemIndex > 0) { keyCode = -3; previousSelectedItemIndex = selectedItemIndex; selectedItemIndex -= 1; } else { if (scrollOffset > 0) { keyCode = -4; scrollOffset -= 1; } } break; case KEY_PAGE_UP: if (scrollOffset > 0) { scrollOffset -= listViewCapacity; if (scrollOffset < 0) { scrollOffset = 0; } keyCode = -4; } break; case KEY_END: if (scrollOffset < itemsLength - listViewCapacity) { keyCode = -4; scrollOffset = itemsLength - listViewCapacity; } break; case KEY_ARROW_DOWN: if (selectedItemIndex < listViewCapacity - 1 && selectedItemIndex < itemsLength - 1) { keyCode = -3; previousSelectedItemIndex = selectedItemIndex; selectedItemIndex += 1; } else { if (scrollOffset + listViewCapacity < itemsLength) { keyCode = -4; scrollOffset += 1; } } break; case KEY_PAGE_DOWN: if (scrollOffset < itemsLength - listViewCapacity) { scrollOffset += listViewCapacity; if (scrollOffset > itemsLength - listViewCapacity) { scrollOffset = itemsLength - listViewCapacity; } keyCode = -4; } break; default: if (itemsLength > listViewCapacity) { if ((keyCode >= 'a' && keyCode <= 'z') || (keyCode >= 'A' && keyCode <= 'Z')) { int found = find_first_letter(keyCode, items, itemsLength); if (found != -1) { scrollOffset = found; if (scrollOffset > itemsLength - listViewCapacity) { scrollOffset = itemsLength - listViewCapacity; } keyCode = -4; selectedItemIndex = found - scrollOffset; } } } break; } } if (keyCode == -4) { buf_fill(listViewBuffer, listViewWidth, listViewMaxY - listViewY, windowWidth, colorTable[GNW_wcolor[0]]); win_text(win, items + scrollOffset, itemsLength < listViewCapacity ? itemsLength : listViewCapacity, listViewWidth, listViewX, listViewY, a7 | 0x2000000); lighten_buf(listViewBuffer + windowWidth * selectedItemIndex * text_height(), listViewWidth, text_height(), windowWidth); if (itemsLength > listViewCapacity) { buf_fill(windowBuffer + windowWidth * scrollbarY + scrollbarX, scrollbarKnobSize + 1, scrollbarKnobSize + 1, windowWidth, colorTable[GNW_wcolor[0]]); scrollbarY = (scrollOffset * (listViewMaxY - listViewY - 2 * text_height() - 16 - scrollbarKnobSize - 1)) / (itemsLength - listViewCapacity) + listViewY + text_height() + 7; draw_shaded_box(windowBuffer, windowWidth, scrollbarX, scrollbarY, scrollbarX + scrollbarKnobSize, scrollbarY + scrollbarKnobSize, colorTable[GNW_wcolor[1]], colorTable[GNW_wcolor[2]]); lighten_buf(windowBuffer + windowWidth * scrollbarY + scrollbarX, scrollbarKnobSize, scrollbarKnobSize, windowWidth); GNW_win_refresh(window, windowRect, NULL); } } else if (keyCode == -3) { Rect itemRect; itemRect.ulx = windowRect->ulx + listViewX; itemRect.lrx = itemRect.ulx + listViewWidth; if (previousSelectedItemIndex != -1) { itemRect.uly = windowRect->uly + listViewY + previousSelectedItemIndex * text_height(); itemRect.lry = itemRect.uly + text_height(); buf_fill(listViewBuffer + windowWidth * previousSelectedItemIndex * text_height(), listViewWidth, text_height(), windowWidth, colorTable[GNW_wcolor[0]]); int color; if ((a7 & 0xFF00) != 0) { int colorIndex = (a7 & 0xFF) - 1; color = (a7 & ~0xFFFF) | colorTable[GNW_wcolor[colorIndex]]; } else { color = a7; } text_to_buf(listViewBuffer + windowWidth * previousSelectedItemIndex * text_height(), items[scrollOffset + previousSelectedItemIndex], windowWidth, windowWidth, color); GNW_win_refresh(window, &itemRect, NULL); } if (selectedItemIndex != -1) { itemRect.uly = windowRect->uly + listViewY + selectedItemIndex * text_height(); itemRect.lry = itemRect.uly + text_height(); lighten_buf(listViewBuffer + windowWidth * selectedItemIndex * text_height(), listViewWidth, text_height(), windowWidth); GNW_win_refresh(window, &itemRect, NULL); } } } win_delete(win); return absoluteSelectedItemIndex; } // 0x4DB478 int win_get_str(char* dest, int length, const char* title, int x, int y) { if (!GNW_win_init_flag) { return -1; } int titleWidth = text_width(title) + 12; if (titleWidth < text_max() * length) { titleWidth = text_max() * length; } int windowWidth = titleWidth + 16; if (windowWidth < 160) { windowWidth = 160; } int windowHeight = 5 * text_height() + 16; int win = win_add(x, y, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { return -1; } win_border(win); unsigned char* windowBuffer = win_get_buf(win); buf_fill(windowBuffer + windowWidth * (text_height() + 14) + 14, windowWidth - 28, text_height() + 2, windowWidth, colorTable[GNW_wcolor[0]]); text_to_buf(windowBuffer + windowWidth * 8 + 8, title, windowWidth, windowWidth, colorTable[GNW_wcolor[4]]); draw_shaded_box(windowBuffer, windowWidth, 14, text_height() + 14, windowWidth - 14, 2 * text_height() + 16, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); win_register_text_button(win, windowWidth / 2 - 72, windowHeight - 8 - text_height() - 6, -1, -1, -1, KEY_RETURN, "Done", 0); win_register_text_button(win, windowWidth / 2 + 8, windowHeight - 8 - text_height() - 6, -1, -1, -1, KEY_ESCAPE, "Cancel", 0); win_draw(win); win_input_str(win, dest, length, 16, text_height() + 16, colorTable[GNW_wcolor[3]], colorTable[GNW_wcolor[0]]); win_delete(win); return 0; } // 0x4DBA98 int win_msg(const char* string, int x, int y, int flags) { if (!GNW_win_init_flag) { return -1; } int windowHeight = 3 * text_height() + 16; int windowWidth = text_width(string) + 16; if (windowWidth < 80) { windowWidth = 80; } windowWidth += 16; int win = win_add(x, y, windowWidth, windowHeight, 256, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { return -1; } win_border(win); Window* window = GNW_find(win); unsigned char* windowBuffer = window->buffer; int color; if ((flags & 0xFF00) != 0) { int index = (flags & 0xFF) - 1; color = colorTable[GNW_wcolor[index]]; color |= flags & ~0xFFFF; } else { color = flags; } text_to_buf(windowBuffer + windowWidth * 8 + 16, string, windowWidth, windowWidth, color); win_register_text_button(win, windowWidth / 2 - 32, windowHeight - 8 - text_height() - 6, -1, -1, -1, KEY_ESCAPE, "Done", 0); win_draw(win); while (get_input() != KEY_ESCAPE) { } win_delete(win); return 0; } // 0x4DBBC4 int win_pull_down(char** items, int itemsLength, int x, int y, int a5) { if (!GNW_win_init_flag) { return -1; } Rect rect; int win = create_pull_down(items, itemsLength, x, y, a5, colorTable[GNW_wcolor[0]], &rect); if (win == -1) { return -1; } return process_pull_down(win, &rect, items, itemsLength, a5, colorTable[GNW_wcolor[0]], NULL, -1); } // 0x4DBC34 static int create_pull_down(char** stringList, int stringListLength, int x, int y, int a5, int a6, Rect* rect) { int windowHeight = stringListLength * text_height() + 16; int windowWidth = win_width_needed(stringList, stringListLength) + 4; if (windowHeight < 2 || windowWidth < 2) { return -1; } int win = win_add(x, y, windowWidth, windowHeight, a6, WINDOW_FLAG_0x10 | WINDOW_FLAG_0x04); if (win == -1) { return -1; } win_text(win, stringList, stringListLength, windowWidth - 4, 2, 8, a5); win_box(win, 0, 0, windowWidth - 1, windowHeight - 1, colorTable[0]); win_box(win, 1, 1, windowWidth - 2, windowHeight - 2, a5); win_draw(win); win_get_rect(win, rect); return win; } // 0x4DC30C int win_debug(char* string) { if (!GNW_win_init_flag) { return -1; } int lineHeight = text_height(); if (wd == -1) { wd = win_add(80, 80, 300, 192, 256, WINDOW_FLAG_0x04); if (wd == -1) { return -1; } win_border(wd); Window* window = GNW_find(wd); unsigned char* windowBuffer = window->buffer; win_fill(wd, 8, 8, 284, lineHeight, 0x100 | 1); win_print(wd, "Debug", 0, (300 - text_width("Debug")) / 2, 8, 0x2000000 | 0x100 | 4); draw_shaded_box(windowBuffer, 300, 8, 8, 291, lineHeight + 8, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); win_fill(wd, 9, 26, 282, 135, 0x100 | 1); draw_shaded_box(windowBuffer, 300, 8, 25, 291, lineHeight + 145, colorTable[GNW_wcolor[2]], colorTable[GNW_wcolor[1]]); currx = 9; curry = 26; int btn = win_register_text_button(wd, (300 - text_width("Close")) / 2, 192 - 8 - lineHeight - 6, -1, -1, -1, -1, "Close", 0); win_register_button_func(btn, NULL, NULL, NULL, win_debug_delete); win_register_button(wd, 8, 8, 284, lineHeight, -1, -1, -1, -1, NULL, NULL, NULL, BUTTON_FLAG_0x10); } char temp[2]; temp[1] = '\0'; char* pch = string; while (*pch != '\0') { int characterWidth = text_char_width(*pch); if (*pch == '\n' || currx + characterWidth > 291) { currx = 9; curry += lineHeight; } while (160 - curry < lineHeight) { Window* window = GNW_find(wd); unsigned char* windowBuffer = window->buffer; buf_to_buf(windowBuffer + lineHeight * 300 + 300 * 26 + 9, 282, 134 - lineHeight - 1, 300, windowBuffer + 300 * 26 + 9, 300); curry -= lineHeight; win_fill(wd, 9, curry, 282, lineHeight, 0x100 | 1); } if (*pch != '\n') { temp[0] = *pch; win_print(wd, temp, 0, currx, curry, 0x2000000 | 0x100 | 4); currx += characterWidth + text_spacing(); } pch++; } win_draw(wd); return 0; } // 0x4DC65C static void win_debug_delete(int btn, int keyCode) { win_delete(wd); wd = -1; } // 0x4DC674 int win_register_menu_bar(int win, int x, int y, int width, int height, int borderColor, int backgroundColor) { Window* window = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (window == NULL) { return -1; } if (window->menuBar != NULL) { return -1; } int right = x + width; if (right > window->width) { return -1; } int bottom = y + height; if (bottom > window->height) { return -1; } MenuBar* menuBar = window->menuBar = (MenuBar*)mem_malloc(sizeof(MenuBar)); if (menuBar == NULL) { return -1; } menuBar->win = win; menuBar->rect.ulx = x; menuBar->rect.uly = y; menuBar->rect.lrx = right - 1; menuBar->rect.lry = bottom - 1; menuBar->pulldownsLength = 0; menuBar->borderColor = borderColor; menuBar->backgroundColor = backgroundColor; win_fill(win, x, y, width, height, backgroundColor); win_box(win, x, y, right - 1, bottom - 1, borderColor); return 0; } // 0x4DC768 int win_register_menu_pulldown(int win, int x, char* title, int keyCode, int itemsLength, char** items, int a7, int a8) { Window* window = GNW_find(win); if (!GNW_win_init_flag) { return -1; } if (window == NULL) { return -1; } MenuBar* menuBar = window->menuBar; if (menuBar == NULL) { return -1; } if (window->menuBar->pulldownsLength == 15) { return -1; } int titleX = menuBar->rect.ulx + x; int titleY = (menuBar->rect.uly + menuBar->rect.lry - text_height()) / 2; int btn = win_register_button(win, titleX, titleY, text_width(title), text_height(), -1, -1, keyCode, -1, NULL, NULL, NULL, 0); if (btn == -1) { return -1; } win_print(win, title, 0, titleX, titleY, window->menuBar->borderColor | 0x2000000); MenuPulldown* pulldown = &(window->menuBar->pulldowns[window->menuBar->pulldownsLength]); pulldown->rect.ulx = titleX; pulldown->rect.uly = titleY; pulldown->rect.lrx = text_width(title) + titleX - 1; pulldown->rect.lry = text_height() + titleY - 1; pulldown->keyCode = keyCode; pulldown->itemsLength = itemsLength; pulldown->items = items; pulldown->field_1C = a7; pulldown->field_20 = a8; window->menuBar->pulldownsLength++; return 0; } // 0x4DC8D0 void win_delete_menu_bar(int win) { Window* window = GNW_find(win); if (!GNW_win_init_flag) { return; } if (window == NULL) { return; } if (window->menuBar == NULL) { return; } win_fill(win, window->menuBar->rect.ulx, window->menuBar->rect.uly, rectGetWidth(&(window->menuBar->rect)), rectGetHeight(&(window->menuBar->rect)), window->field_20); mem_free(window->menuBar); window->menuBar = NULL; } // 0x4DC9F0 static int find_first_letter(int ch, char** stringList, int stringListLength) { if (ch >= 'A' && ch <= 'Z') { ch += ' '; } for (int index = 0; index < stringListLength; index++) { char* string = stringList[index]; if (string[0] == ch || string[0] == ch - ' ') { return index; } } return -1; } // 0x4DCA30 int win_width_needed(char** fileNameList, int fileNameListLength) { int maxWidth = 0; for (int index = 0; index < fileNameListLength; index++) { int width = text_width(fileNameList[index]); if (width > maxWidth) { maxWidth = width; } } return maxWidth; } // 0x4DCA5C int win_input_str(int win, char* dest, int maxLength, int x, int y, int textColor, int backgroundColor) { Window* window = GNW_find(win); unsigned char* buffer = window->buffer + window->width * y + x; int cursorPos = strlen(dest); dest[cursorPos] = '_'; dest[cursorPos + 1] = '\0'; int lineHeight = text_height(); int stringWidth = text_width(dest); buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor); text_to_buf(buffer, dest, stringWidth, window->width, textColor); Rect dirtyRect; dirtyRect.ulx = window->rect.ulx + x; dirtyRect.uly = window->rect.uly + y; dirtyRect.lrx = dirtyRect.ulx + stringWidth; dirtyRect.lry = dirtyRect.uly + lineHeight; GNW_win_refresh(window, &dirtyRect, NULL); // NOTE: This loop is slightly different compared to other input handling // loops. Cursor position is managed inside an incrementing loop. Cursor is // decremented in the loop body when key is not handled. bool isFirstKey = true; for (; cursorPos <= maxLength; cursorPos++) { int keyCode = get_input(); if (keyCode != -1) { if (keyCode == KEY_ESCAPE) { dest[cursorPos] = '\0'; return -1; } if (keyCode == KEY_BACKSPACE) { if (cursorPos > 0) { stringWidth = text_width(dest); if (isFirstKey) { buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor); dirtyRect.ulx = window->rect.ulx + x; dirtyRect.uly = window->rect.uly + y; dirtyRect.lrx = dirtyRect.ulx + stringWidth; dirtyRect.lry = dirtyRect.uly + lineHeight; GNW_win_refresh(window, &dirtyRect, NULL); dest[0] = '_'; dest[1] = '\0'; cursorPos = 1; } else { dest[cursorPos] = ' '; dest[cursorPos - 1] = '_'; } buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor); text_to_buf(buffer, dest, stringWidth, window->width, textColor); dirtyRect.ulx = window->rect.ulx + x; dirtyRect.uly = window->rect.uly + y; dirtyRect.lrx = dirtyRect.ulx + stringWidth; dirtyRect.lry = dirtyRect.uly + lineHeight; GNW_win_refresh(window, &dirtyRect, NULL); dest[cursorPos] = '\0'; cursorPos -= 2; isFirstKey = false; } else { cursorPos--; } } else if (keyCode == KEY_RETURN) { break; } else { if (cursorPos == maxLength) { cursorPos = maxLength - 1; } else { if (keyCode > 0 && keyCode < 256) { dest[cursorPos] = keyCode; dest[cursorPos + 1] = '_'; dest[cursorPos + 2] = '\0'; int stringWidth = text_width(dest); buf_fill(buffer, stringWidth, lineHeight, window->width, backgroundColor); text_to_buf(buffer, dest, stringWidth, window->width, textColor); dirtyRect.ulx = window->rect.ulx + x; dirtyRect.uly = window->rect.uly + y; dirtyRect.lrx = dirtyRect.ulx + stringWidth; dirtyRect.lry = dirtyRect.uly + lineHeight; GNW_win_refresh(window, &dirtyRect, NULL); isFirstKey = false; } else { cursorPos--; } } } } else { cursorPos--; } } dest[cursorPos] = '\0'; return 0; } // 0x4DBD04 static int process_pull_down(int win, Rect* rect, char** items, int itemsLength, int a5, int a6, MenuBar* menuBar, int pulldownIndex) { // TODO: Incomplete. return -1; } // 0x4DC930 int GNW_process_menu(MenuBar* menuBar, int pulldownIndex) { // 0x51E418 static MenuBar* curr_menu = NULL; if (curr_menu != NULL) { return -1; } curr_menu = menuBar; int keyCode; Rect rect; do { MenuPulldown* pulldown = &(menuBar->pulldowns[pulldownIndex]); int win = create_pull_down(pulldown->items, pulldown->itemsLength, pulldown->rect.ulx, menuBar->rect.lry + 1, pulldown->field_1C, pulldown->field_20, &rect); if (win == -1) { curr_menu = NULL; return -1; } keyCode = process_pull_down(win, &rect, pulldown->items, pulldown->itemsLength, pulldown->field_1C, pulldown->field_20, menuBar, pulldownIndex); if (keyCode < -1) { pulldownIndex = -2 - keyCode; } } while (keyCode < -1); if (keyCode != -1) { flush_input_buffer(); GNW_add_input_buffer(keyCode); keyCode = menuBar->pulldowns[pulldownIndex].keyCode; } curr_menu = NULL; return keyCode; } // Calculates max length of string needed to represent a1 or a2. // // 0x4DD03C static int calc_max_field_chars_wcursor(int a1, int a2) { char* str = (char*)mem_malloc(17); if (str == NULL) { return -1; } sprintf(str, "%d", a1); int len1 = strlen(str); sprintf(str, "%d", a2); int len2 = strlen(str); mem_free(str); return max(len1, len2) + 1; } // 0x4DD3EC void GNW_intr_init() { int v1, v2; int i; tm_persistence = 3000; tm_add = 0; tm_kill = -1; scr_center_x = scr_size.lrx / 2; if (scr_size.lry >= 479) { tm_text_y = 16; tm_text_x = 16; } else { tm_text_y = 10; tm_text_x = 10; } tm_h = 2 * tm_text_y + text_height(); v1 = scr_size.lry >> 3; v2 = scr_size.lry >> 2; for (i = 0; i < 5; i++) { tm_location[i].y = v1 * i + v2; tm_location[i].taken = 0; } } // 0x4DD4A4 void GNW_intr_exit() { remove_bk_process(tm_watch_msgs); while (tm_kill != -1) { tm_kill_msg(); } } // 0x4DD66C static void tm_watch_msgs() { if (tm_watch_active) { return; } tm_watch_active = 1; while (tm_kill != -1) { if (elapsed_time(tm_queue[tm_kill].created) < tm_persistence) { break; } tm_kill_msg(); } tm_watch_active = 0; } // 0x4DD6C0 static void tm_kill_msg() { int v0; v0 = tm_kill; if (v0 != -1) { win_delete(tm_queue[tm_kill].id); tm_location[tm_queue[tm_kill].location].taken = 0; if (v0 == 5) { v0 = 0; } if (v0 == tm_add) { tm_add = 0; tm_kill = -1; remove_bk_process(tm_watch_msgs); v0 = tm_kill; } } tm_kill = v0; } // 0x4DD744 static void tm_kill_out_of_order(int a1) { int v7; int v6; if (tm_kill == -1) { return; } if (!tm_index_active(a1)) { return; } win_delete(tm_queue[a1].id); tm_location[tm_queue[a1].location].taken = 0; if (a1 != tm_kill) { v6 = a1; do { v7 = v6 - 1; if (v7 < 0) { v7 = 4; } tm_queue[v6] = tm_queue[v7]; v6 = v7; } while (v7 != tm_kill); } if (++tm_kill == 5) { tm_kill = 0; } if (tm_add == tm_kill) { tm_add = 0; tm_kill = -1; remove_bk_process(tm_watch_msgs); } } // 0x4DD82C static void tm_click_response(int btn) { int win; int v3; if (tm_kill == -1) { return; } win = win_button_winID(btn); v3 = tm_kill; while (win != tm_queue[v3].id) { v3++; if (v3 == 5) { v3 = 0; } if (v3 == tm_kill || !tm_index_active(v3)) return; } tm_kill_out_of_order(v3); } // 0x4DD870 static int tm_index_active(int a1) { if (tm_kill != tm_add) { if (tm_kill >= tm_add) { if (a1 >= tm_add && a1 < tm_kill) return 0; } else if (a1 < tm_kill || a1 >= tm_add) { return 0; } } return 1; } ================================================ FILE: src/plib/gnw/intrface.h ================================================ #ifndef FALLOUT_PLIB_GNW_INTRFACE_H_ #define FALLOUT_PLIB_GNW_INTRFACE_H_ #include #include "plib/gnw/rect.h" typedef struct MenuBar MenuBar; typedef void(SelectFunc)(char** items, int index); int win_list_select(const char* title, char** fileList, int fileListLength, SelectFunc* callback, int x, int y, int a7); int win_list_select_at(const char* title, char** fileList, int fileListLength, SelectFunc* callback, int x, int y, int a7, int a8); int win_get_str(char* dest, int length, const char* title, int x, int y); int win_msg(const char* string, int x, int y, int flags); int win_pull_down(char** items, int itemsLength, int x, int y, int a5); int win_debug(char* string); int win_register_menu_bar(int win, int x, int y, int width, int height, int borderColor, int backgroundColor); int win_register_menu_pulldown(int win, int x, char* title, int keyCode, int itemsLength, char** items, int a7, int a8); void win_delete_menu_bar(int win); int win_width_needed(char** fileNameList, int fileNameListLength); int win_input_str(int win, char* dest, int maxLength, int x, int y, int textColor, int backgroundColor); int GNW_process_menu(MenuBar* menuBar, int pulldownIndex); void GNW_intr_init(); void GNW_intr_exit(); #endif /* FALLOUT_PLIB_GNW_INTRFACE_H_ */ ================================================ FILE: src/plib/gnw/kb.c ================================================ #include "plib/gnw/kb.h" #include "plib/gnw/input.h" #include "plib/gnw/dxinput.h" #include "plib/gnw/gnw95dx.h" #include "plib/gnw/vcr.h" typedef struct key_ansi_t { short keys; short normal; short shift; short left_alt; short right_alt; short ctrl; } key_ansi_t; typedef struct key_data_t { unsigned char scan_code; unsigned short modifiers; } key_data_t; static int kb_next_ascii_English_US(); static int kb_next_ascii(); static void kb_map_ascii_English_US(); static void kb_map_ascii_French(); static void kb_map_ascii_German(); static void kb_map_ascii_Italian(); static void kb_map_ascii_Spanish(); static void kb_init_lock_status(); static void kb_toggle_caps(); static void kb_toggle_num(); static void kb_toggle_scroll(); static void kb_buffer_put(); static void kb_buffer_get(); static int kb_buffer_peek(int index, key_data_t** keyboardEventPtr); // 0x51E2D0 static unsigned char kb_installed = 0; // 0x51E2D4 static bool kb_disabled = false; // 0x51E2D8 static bool kb_numpad_disabled = false; // 0x51E2DC static bool kb_numlock_disabled = false; // 0x51E2E0 static int kb_put = 0; // 0x51E2E4 static int kb_get = 0; // 0x51E2E8 static short extended_code = 0; // 0x51E2EA static int kb_lock_flags = 0; // 0x51E2EC static int (*kb_scan_to_ascii)() = kb_next_ascii_English_US; // Ring buffer of keyboard events. // // 0x6ACB30 static key_data_t kb_buffer[64]; // A map of logical key configurations for physical scan codes [DIK_*]. // // 0x6ACC30 static key_ansi_t ascii_table[256]; // A state of physical keys [DIK_*] currently pressed. // // 0 - key is not pressed. // 1 - key pressed. // // 0x6AD830 unsigned char keys[256]; // 0x6AD930 static unsigned int kb_idle_start_time; // 0x6AD934 static key_data_t temp; // 0x6AD938 int kb_layout; // The number of keys currently pressed. // // 0x6AD93C unsigned char keynumpress; // 0x4CBC90 int GNW_kb_set() { if (kb_installed) { return -1; } kb_installed = 1; // NOTE: Uninline. kb_clear(); kb_init_lock_status(); kb_set_layout(KEYBOARD_LAYOUT_QWERTY); kb_idle_start_time = get_time(); return 0; } // 0x4CBD00 void GNW_kb_restore() { if (kb_installed) { kb_installed = 0; } } // NOTE: Unused. // // 0x4CBD14 void kb_wait() { if (kb_installed) { // NOTE: Uninline. kb_clear(); do { GNW95_process_message(); } while (keynumpress == 0); // NOTE: Uninline. kb_clear(); } } // 0x4CBDA8 void kb_clear() { int i; if (kb_installed) { keynumpress = 0; for (i = 0; i < 256; i++) { keys[i] = 0; } kb_put = 0; kb_get = 0; } dxinput_flush_keyboard_buffer(); GNW95_clear_time_stamps(); } // 0x4CBDE8 int kb_getch() { int rc = -1; if (kb_installed != 0) { rc = kb_scan_to_ascii(); } return rc; } // 0x4CBE00 void kb_disable() { kb_disabled = true; } // 0x4CBE0C void kb_enable() { kb_disabled = false; } // 0x4CBE18 bool kb_is_disabled() { return kb_disabled; } // NOTE: Unused. // // 0x4CBE20 void kb_disable_numpad() { kb_numpad_disabled = true; } // NOTE: Unused. // // 0x4CBE2C void kb_enable_numpad() { kb_numpad_disabled = false; } // NOTE: Unused. // // 0x4CBE38 bool kb_numpad_is_disabled() { return kb_numpad_disabled; } // NOTE: Unused. // // 0x4CBE40 void kb_disable_numlock() { kb_numlock_disabled = true; } // NOTE: Unused. // // 0x4CBE4C void kb_enable_numlock() { kb_numlock_disabled = false; } // NOTE: Unused. // // 0x4CBE58 bool kb_numlock_is_disabled() { return kb_numlock_disabled; } // 0x4CBE74 void kb_set_layout(int layout) { int old_layout = kb_layout; kb_layout = layout; switch (layout) { case KEYBOARD_LAYOUT_QWERTY: kb_scan_to_ascii = kb_next_ascii_English_US; kb_map_ascii_English_US(); break; // case KEYBOARD_LAYOUT_FRENCH: // kb_scan_to_ascii = sub_4CC5BC; // _kb_map_ascii_French(); // break; // case KEYBOARD_LAYOUT_GERMAN: // kb_scan_to_ascii = sub_4CC94C; // _kb_map_ascii_German(); // break; // case KEYBOARD_LAYOUT_ITALIAN: // kb_scan_to_ascii = sub_4CCE14; // _kb_map_ascii_Italian(); // break; // case KEYBOARD_LAYOUT_SPANISH: // kb_scan_to_ascii = sub_4CD0E0; // _kb_map_ascii_Spanish(); // break; default: kb_layout = old_layout; break; } } // 0x4CBEEC int kb_get_layout() { return kb_layout; } // NOTE: Unused. // // 0x4CBEF4 int kb_ascii_to_scan(int ascii) { int k; for (k = 0; k < 256; k++) { if (ascii_table[k].normal == k || ascii_table[k].shift == k || ascii_table[k].left_alt == k || ascii_table[k].right_alt == k || ascii_table[k].ctrl == k) { return k; } } return -1; } // NOTE: Unused. // // 0x4CBF50 unsigned int kb_elapsed_time() { return elapsed_time(kb_idle_start_time); } // NOTE: Unused. // // 0x4CBF5C void kb_reset_elapsed_time() { kb_idle_start_time = get_time(); } // TODO: Key type is likely short. // // 0x4CBF68 void kb_simulate_key(int scan_code) { if (vcr_state == 0) { if (vcr_buffer_index != VCR_BUFFER_CAPACITY - 1) { VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]); vcrEntry->type = VCR_ENTRY_TYPE_KEYBOARD_EVENT; vcrEntry->keyboardEvent.key = scan_code & 0xFFFF; vcrEntry->time = vcr_time; vcrEntry->counter = vcr_counter; vcr_buffer_index++; } } kb_idle_start_time = get_bk_time(); if (scan_code == 224) { extended_code = 0x80; } else { int keyState; if (scan_code & 0x80) { scan_code &= ~0x80; keyState = KEY_STATE_UP; } else { keyState = KEY_STATE_DOWN; } int physicalKey = scan_code | extended_code; if (keyState != KEY_STATE_UP && keys[physicalKey] != KEY_STATE_UP) { keyState = KEY_STATE_REPEAT; } if (keys[physicalKey] != keyState) { keys[physicalKey] = keyState; if (keyState == KEY_STATE_DOWN) { keynumpress++; } else if (keyState == KEY_STATE_UP) { keynumpress--; } } if (keyState != KEY_STATE_UP) { temp.scan_code = physicalKey & 0xFF; temp.modifiers = 0; if (physicalKey == DIK_CAPITAL) { if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) { // NOTE: Uninline. kb_toggle_caps(); } } else if (physicalKey == DIK_NUMLOCK) { if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) { // NOTE: Uninline. kb_toggle_num(); } } else if (physicalKey == DIK_SCROLL) { if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) { // NOTE: Uninline. kb_toggle_scroll(); } } else if ((physicalKey == DIK_LSHIFT || physicalKey == DIK_RSHIFT) && (kb_lock_flags & MODIFIER_KEY_STATE_CAPS_LOCK) != 0 && kb_layout != 0) { if (keys[DIK_LCONTROL] == KEY_STATE_UP && keys[DIK_RCONTROL] == KEY_STATE_UP) { // NOTE: Uninline. kb_toggle_caps(); } } if (kb_lock_flags != 0) { if ((kb_lock_flags & MODIFIER_KEY_STATE_NUM_LOCK) != 0 && !kb_numlock_disabled) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_NUM_LOCK; } if ((kb_lock_flags & MODIFIER_KEY_STATE_CAPS_LOCK) != 0) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_CAPS_LOCK; } if ((kb_lock_flags & MODIFIER_KEY_STATE_SCROLL_LOCK) != 0) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_SCROLL_LOCK; } } if (keys[DIK_LSHIFT] != KEY_STATE_UP) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT; } if (keys[DIK_RSHIFT] != KEY_STATE_UP) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT; } if (keys[DIK_LMENU] != KEY_STATE_UP) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_ALT; } if (keys[DIK_RMENU] != KEY_STATE_UP) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_ALT; } if (keys[DIK_LCONTROL] != KEY_STATE_UP) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL; } if (keys[DIK_RCONTROL] != KEY_STATE_UP) { temp.modifiers |= KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL; } if (((kb_put + 1) & 0x3F) != kb_get) { kb_buffer[kb_put] = temp; kb_put++; kb_put &= 0x3F; } } extended_code = 0; } if (keys[198] != KEY_STATE_UP) { // NOTE: Uninline kb_clear(); } } // 0x4CC2F0 static int kb_next_ascii_English_US() { key_data_t* keyboardEvent; if (kb_buffer_peek(0, &keyboardEvent) != 0) { return -1; } if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_CAPS_LOCK) != 0) { unsigned char a = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_A : DIK_Q); unsigned char m = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_M : DIK_SEMICOLON); unsigned char q = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_Q : DIK_A); unsigned char w = (kb_layout != KEYBOARD_LAYOUT_FRENCH ? DIK_W : DIK_Z); unsigned char y; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_FRENCH: case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: y = DIK_Y; break; default: // GERMAN y = DIK_Z; break; } unsigned char z; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: z = DIK_Z; break; case KEYBOARD_LAYOUT_FRENCH: z = DIK_W; break; default: // GERMAN z = DIK_Y; break; } unsigned char scanCode = keyboardEvent->scan_code; if (scanCode == a || scanCode == DIK_B || scanCode == DIK_C || scanCode == DIK_D || scanCode == DIK_E || scanCode == DIK_F || scanCode == DIK_G || scanCode == DIK_H || scanCode == DIK_I || scanCode == DIK_J || scanCode == DIK_K || scanCode == DIK_L || scanCode == m || scanCode == DIK_N || scanCode == DIK_O || scanCode == DIK_P || scanCode == q || scanCode == DIK_R || scanCode == DIK_S || scanCode == DIK_T || scanCode == DIK_U || scanCode == DIK_V || scanCode == w || scanCode == DIK_X || scanCode == y || scanCode == z) { if (keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) { keyboardEvent->modifiers &= ~KEYBOARD_EVENT_MODIFIER_ANY_SHIFT; } else { keyboardEvent->modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT; } } } return kb_next_ascii(); } // 0x4CDA4C static int kb_next_ascii() { key_data_t* keyboardEvent; if (kb_buffer_peek(0, &keyboardEvent) != 0) { return -1; } switch (keyboardEvent->scan_code) { case DIK_DIVIDE: case DIK_MULTIPLY: case DIK_SUBTRACT: case DIK_ADD: case DIK_NUMPADENTER: if (kb_numpad_disabled) { if (kb_get != kb_put) { kb_get++; kb_get &= (KEY_QUEUE_SIZE - 1); } return -1; } break; case DIK_NUMPAD0: case DIK_NUMPAD1: case DIK_NUMPAD2: case DIK_NUMPAD3: case DIK_NUMPAD4: case DIK_NUMPAD5: case DIK_NUMPAD6: case DIK_NUMPAD7: case DIK_NUMPAD8: case DIK_NUMPAD9: if (kb_numpad_disabled) { if (kb_get != kb_put) { kb_get++; kb_get &= (KEY_QUEUE_SIZE - 1); } return -1; } if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_ALT) == 0 && (keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_NUM_LOCK) != 0) { if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) != 0) { keyboardEvent->modifiers &= ~KEYBOARD_EVENT_MODIFIER_ANY_SHIFT; } else { keyboardEvent->modifiers |= KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT; } } break; } int logicalKey = -1; key_ansi_t* logicalKeyDescription = &(ascii_table[keyboardEvent->scan_code]); if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_CONTROL) != 0) { logicalKey = logicalKeyDescription->ctrl; } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_RIGHT_ALT) != 0) { logicalKey = logicalKeyDescription->right_alt; } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_LEFT_ALT) != 0) { logicalKey = logicalKeyDescription->left_alt; } else if ((keyboardEvent->modifiers & KEYBOARD_EVENT_MODIFIER_ANY_SHIFT) != 0) { logicalKey = logicalKeyDescription->shift; } else { logicalKey = logicalKeyDescription->normal; } if (kb_get != kb_put) { kb_get++; kb_get &= (KEY_QUEUE_SIZE - 1); } return logicalKey; } // 0x4CDC08 static void kb_map_ascii_English_US() { int k; for (k = 0; k < 256; k++) { ascii_table[k].keys = -1; ascii_table[k].normal = -1; ascii_table[k].shift = -1; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; } ascii_table[DIK_ESCAPE].normal = KEY_ESCAPE; ascii_table[DIK_ESCAPE].shift = KEY_ESCAPE; ascii_table[DIK_ESCAPE].left_alt = KEY_ESCAPE; ascii_table[DIK_ESCAPE].right_alt = KEY_ESCAPE; ascii_table[DIK_ESCAPE].ctrl = KEY_ESCAPE; ascii_table[DIK_F1].normal = KEY_F1; ascii_table[DIK_F1].shift = KEY_SHIFT_F1; ascii_table[DIK_F1].left_alt = KEY_ALT_F1; ascii_table[DIK_F1].right_alt = KEY_ALT_F1; ascii_table[DIK_F1].ctrl = KEY_CTRL_F1; ascii_table[DIK_F2].normal = KEY_F2; ascii_table[DIK_F2].shift = KEY_SHIFT_F2; ascii_table[DIK_F2].left_alt = KEY_ALT_F2; ascii_table[DIK_F2].right_alt = KEY_ALT_F2; ascii_table[DIK_F2].ctrl = KEY_CTRL_F2; ascii_table[DIK_F3].normal = KEY_F3; ascii_table[DIK_F3].shift = KEY_SHIFT_F3; ascii_table[DIK_F3].left_alt = KEY_ALT_F3; ascii_table[DIK_F3].right_alt = KEY_ALT_F3; ascii_table[DIK_F3].ctrl = KEY_CTRL_F3; ascii_table[DIK_F4].normal = KEY_F4; ascii_table[DIK_F4].shift = KEY_SHIFT_F4; ascii_table[DIK_F4].left_alt = KEY_ALT_F4; ascii_table[DIK_F4].right_alt = KEY_ALT_F4; ascii_table[DIK_F4].ctrl = KEY_CTRL_F4; ascii_table[DIK_F5].normal = KEY_F5; ascii_table[DIK_F5].shift = KEY_SHIFT_F5; ascii_table[DIK_F5].left_alt = KEY_ALT_F5; ascii_table[DIK_F5].right_alt = KEY_ALT_F5; ascii_table[DIK_F5].ctrl = KEY_CTRL_F5; ascii_table[DIK_F6].normal = KEY_F6; ascii_table[DIK_F6].shift = KEY_SHIFT_F6; ascii_table[DIK_F6].left_alt = KEY_ALT_F6; ascii_table[DIK_F6].right_alt = KEY_ALT_F6; ascii_table[DIK_F6].ctrl = KEY_CTRL_F6; ascii_table[DIK_F7].normal = KEY_F7; ascii_table[DIK_F7].shift = KEY_SHIFT_F7; ascii_table[DIK_F7].left_alt = KEY_ALT_F7; ascii_table[DIK_F7].right_alt = KEY_ALT_F7; ascii_table[DIK_F7].ctrl = KEY_CTRL_F7; ascii_table[DIK_F8].normal = KEY_F8; ascii_table[DIK_F8].shift = KEY_SHIFT_F8; ascii_table[DIK_F8].left_alt = KEY_ALT_F8; ascii_table[DIK_F8].right_alt = KEY_ALT_F8; ascii_table[DIK_F8].ctrl = KEY_CTRL_F8; ascii_table[DIK_F9].normal = KEY_F9; ascii_table[DIK_F9].shift = KEY_SHIFT_F9; ascii_table[DIK_F9].left_alt = KEY_ALT_F9; ascii_table[DIK_F9].right_alt = KEY_ALT_F9; ascii_table[DIK_F9].ctrl = KEY_CTRL_F9; ascii_table[DIK_F10].normal = KEY_F10; ascii_table[DIK_F10].shift = KEY_SHIFT_F10; ascii_table[DIK_F10].left_alt = KEY_ALT_F10; ascii_table[DIK_F10].right_alt = KEY_ALT_F10; ascii_table[DIK_F10].ctrl = KEY_CTRL_F10; ascii_table[DIK_F11].normal = KEY_F11; ascii_table[DIK_F11].shift = KEY_SHIFT_F11; ascii_table[DIK_F11].left_alt = KEY_ALT_F11; ascii_table[DIK_F11].right_alt = KEY_ALT_F11; ascii_table[DIK_F11].ctrl = KEY_CTRL_F11; ascii_table[DIK_F12].normal = KEY_F12; ascii_table[DIK_F12].shift = KEY_SHIFT_F12; ascii_table[DIK_F12].left_alt = KEY_ALT_F12; ascii_table[DIK_F12].right_alt = KEY_ALT_F12; ascii_table[DIK_F12].ctrl = KEY_CTRL_F12; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_GRAVE; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_2; break; case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: k = 0; break; default: k = DIK_RBRACKET; break; } ascii_table[k].normal = KEY_GRAVE; ascii_table[k].shift = KEY_TILDE; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; ascii_table[DIK_1].normal = KEY_1; ascii_table[DIK_1].shift = KEY_EXCLAMATION; ascii_table[DIK_1].left_alt = -1; ascii_table[DIK_1].right_alt = -1; ascii_table[DIK_1].ctrl = -1; ascii_table[DIK_2].normal = KEY_2; ascii_table[DIK_2].shift = KEY_AT; ascii_table[DIK_2].left_alt = -1; ascii_table[DIK_2].right_alt = -1; ascii_table[DIK_2].ctrl = -1; ascii_table[DIK_3].normal = KEY_3; ascii_table[DIK_3].shift = KEY_NUMBER_SIGN; ascii_table[DIK_3].left_alt = -1; ascii_table[DIK_3].right_alt = -1; ascii_table[DIK_3].ctrl = -1; ascii_table[DIK_4].normal = KEY_4; ascii_table[DIK_4].shift = KEY_DOLLAR; ascii_table[DIK_4].left_alt = -1; ascii_table[DIK_4].right_alt = -1; ascii_table[DIK_4].ctrl = -1; ascii_table[DIK_5].normal = KEY_5; ascii_table[DIK_5].shift = KEY_PERCENT; ascii_table[DIK_5].left_alt = -1; ascii_table[DIK_5].right_alt = -1; ascii_table[DIK_5].ctrl = -1; ascii_table[DIK_6].normal = KEY_6; ascii_table[DIK_6].shift = KEY_CARET; ascii_table[DIK_6].left_alt = -1; ascii_table[DIK_6].right_alt = -1; ascii_table[DIK_6].ctrl = -1; ascii_table[DIK_7].normal = KEY_7; ascii_table[DIK_7].shift = KEY_AMPERSAND; ascii_table[DIK_7].left_alt = -1; ascii_table[DIK_7].right_alt = -1; ascii_table[DIK_7].ctrl = -1; ascii_table[DIK_8].normal = KEY_8; ascii_table[DIK_8].shift = KEY_ASTERISK; ascii_table[DIK_8].left_alt = -1; ascii_table[DIK_8].right_alt = -1; ascii_table[DIK_8].ctrl = -1; ascii_table[DIK_9].normal = KEY_9; ascii_table[DIK_9].shift = KEY_PAREN_LEFT; ascii_table[DIK_9].left_alt = -1; ascii_table[DIK_9].right_alt = -1; ascii_table[DIK_9].ctrl = -1; ascii_table[DIK_0].normal = KEY_0; ascii_table[DIK_0].shift = KEY_PAREN_RIGHT; ascii_table[DIK_0].left_alt = -1; ascii_table[DIK_0].right_alt = -1; ascii_table[DIK_0].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_MINUS; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_6; break; default: k = DIK_SLASH; break; } ascii_table[k].normal = KEY_MINUS; ascii_table[k].shift = KEY_UNDERSCORE; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_FRENCH: k = DIK_EQUALS; break; default: k = DIK_0; break; } ascii_table[k].normal = KEY_EQUAL; ascii_table[k].shift = KEY_PLUS; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; ascii_table[DIK_BACK].normal = KEY_BACKSPACE; ascii_table[DIK_BACK].shift = KEY_BACKSPACE; ascii_table[DIK_BACK].left_alt = KEY_BACKSPACE; ascii_table[DIK_BACK].right_alt = KEY_BACKSPACE; ascii_table[DIK_BACK].ctrl = KEY_DEL; ascii_table[DIK_TAB].normal = KEY_TAB; ascii_table[DIK_TAB].shift = KEY_TAB; ascii_table[DIK_TAB].left_alt = KEY_TAB; ascii_table[DIK_TAB].right_alt = KEY_TAB; ascii_table[DIK_TAB].ctrl = KEY_TAB; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_A; break; default: k = DIK_Q; break; } ascii_table[k].normal = KEY_LOWERCASE_Q; ascii_table[k].shift = KEY_UPPERCASE_Q; ascii_table[k].left_alt = KEY_ALT_Q; ascii_table[k].right_alt = KEY_ALT_Q; ascii_table[k].ctrl = KEY_CTRL_Q; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_Z; break; default: k = DIK_W; break; } ascii_table[k].normal = KEY_LOWERCASE_W; ascii_table[k].shift = KEY_UPPERCASE_W; ascii_table[k].left_alt = KEY_ALT_W; ascii_table[k].right_alt = KEY_ALT_W; ascii_table[k].ctrl = KEY_CTRL_W; ascii_table[DIK_E].normal = KEY_LOWERCASE_E; ascii_table[DIK_E].shift = KEY_UPPERCASE_E; ascii_table[DIK_E].left_alt = KEY_ALT_E; ascii_table[DIK_E].right_alt = KEY_ALT_E; ascii_table[DIK_E].ctrl = KEY_CTRL_E; ascii_table[DIK_R].normal = KEY_LOWERCASE_R; ascii_table[DIK_R].shift = KEY_UPPERCASE_R; ascii_table[DIK_R].left_alt = KEY_ALT_R; ascii_table[DIK_R].right_alt = KEY_ALT_R; ascii_table[DIK_R].ctrl = KEY_CTRL_R; ascii_table[DIK_T].normal = KEY_LOWERCASE_T; ascii_table[DIK_T].shift = KEY_UPPERCASE_T; ascii_table[DIK_T].left_alt = KEY_ALT_T; ascii_table[DIK_T].right_alt = KEY_ALT_T; ascii_table[DIK_T].ctrl = KEY_CTRL_T; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_FRENCH: case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: k = DIK_Y; break; default: k = DIK_Z; break; } ascii_table[k].normal = KEY_LOWERCASE_Y; ascii_table[k].shift = KEY_UPPERCASE_Y; ascii_table[k].left_alt = KEY_ALT_Y; ascii_table[k].right_alt = KEY_ALT_Y; ascii_table[k].ctrl = KEY_CTRL_Y; ascii_table[DIK_U].normal = KEY_LOWERCASE_U; ascii_table[DIK_U].shift = KEY_UPPERCASE_U; ascii_table[DIK_U].left_alt = KEY_ALT_U; ascii_table[DIK_U].right_alt = KEY_ALT_U; ascii_table[DIK_U].ctrl = KEY_CTRL_U; ascii_table[DIK_I].normal = KEY_LOWERCASE_I; ascii_table[DIK_I].shift = KEY_UPPERCASE_I; ascii_table[DIK_I].left_alt = KEY_ALT_I; ascii_table[DIK_I].right_alt = KEY_ALT_I; ascii_table[DIK_I].ctrl = KEY_CTRL_I; ascii_table[DIK_O].normal = KEY_LOWERCASE_O; ascii_table[DIK_O].shift = KEY_UPPERCASE_O; ascii_table[DIK_O].left_alt = KEY_ALT_O; ascii_table[DIK_O].right_alt = KEY_ALT_O; ascii_table[DIK_O].ctrl = KEY_CTRL_O; ascii_table[DIK_P].normal = KEY_LOWERCASE_P; ascii_table[DIK_P].shift = KEY_UPPERCASE_P; ascii_table[DIK_P].left_alt = KEY_ALT_P; ascii_table[DIK_P].right_alt = KEY_ALT_P; ascii_table[DIK_P].ctrl = KEY_CTRL_P; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: k = DIK_LBRACKET; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_5; break; default: k = DIK_8; break; } ascii_table[k].normal = KEY_BRACKET_LEFT; ascii_table[k].shift = KEY_BRACE_LEFT; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: k = DIK_RBRACKET; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_MINUS; break; default: k = DIK_9; break; } ascii_table[k].normal = KEY_BRACKET_RIGHT; ascii_table[k].shift = KEY_BRACE_RIGHT; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_BACKSLASH; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_8; break; case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: k = DIK_GRAVE; break; default: k = DIK_MINUS; break; } ascii_table[k].normal = KEY_BACKSLASH; ascii_table[k].shift = KEY_BAR; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = KEY_CTRL_BACKSLASH; ascii_table[DIK_CAPITAL].normal = -1; ascii_table[DIK_CAPITAL].shift = -1; ascii_table[DIK_CAPITAL].left_alt = -1; ascii_table[DIK_CAPITAL].right_alt = -1; ascii_table[DIK_CAPITAL].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_Q; break; default: k = DIK_A; break; } ascii_table[k].normal = KEY_LOWERCASE_A; ascii_table[k].shift = KEY_UPPERCASE_A; ascii_table[k].left_alt = KEY_ALT_A; ascii_table[k].right_alt = KEY_ALT_A; ascii_table[k].ctrl = KEY_CTRL_A; ascii_table[DIK_S].normal = KEY_LOWERCASE_S; ascii_table[DIK_S].shift = KEY_UPPERCASE_S; ascii_table[DIK_S].left_alt = KEY_ALT_S; ascii_table[DIK_S].right_alt = KEY_ALT_S; ascii_table[DIK_S].ctrl = KEY_CTRL_S; ascii_table[DIK_D].normal = KEY_LOWERCASE_D; ascii_table[DIK_D].shift = KEY_UPPERCASE_D; ascii_table[DIK_D].left_alt = KEY_ALT_D; ascii_table[DIK_D].right_alt = KEY_ALT_D; ascii_table[DIK_D].ctrl = KEY_CTRL_D; ascii_table[DIK_F].normal = KEY_LOWERCASE_F; ascii_table[DIK_F].shift = KEY_UPPERCASE_F; ascii_table[DIK_F].left_alt = KEY_ALT_F; ascii_table[DIK_F].right_alt = KEY_ALT_F; ascii_table[DIK_F].ctrl = KEY_CTRL_F; ascii_table[DIK_G].normal = KEY_LOWERCASE_G; ascii_table[DIK_G].shift = KEY_UPPERCASE_G; ascii_table[DIK_G].left_alt = KEY_ALT_G; ascii_table[DIK_G].right_alt = KEY_ALT_G; ascii_table[DIK_G].ctrl = KEY_CTRL_G; ascii_table[DIK_H].normal = KEY_LOWERCASE_H; ascii_table[DIK_H].shift = KEY_UPPERCASE_H; ascii_table[DIK_H].left_alt = KEY_ALT_H; ascii_table[DIK_H].right_alt = KEY_ALT_H; ascii_table[DIK_H].ctrl = KEY_CTRL_H; ascii_table[DIK_J].normal = KEY_LOWERCASE_J; ascii_table[DIK_J].shift = KEY_UPPERCASE_J; ascii_table[DIK_J].left_alt = KEY_ALT_J; ascii_table[DIK_J].right_alt = KEY_ALT_J; ascii_table[DIK_J].ctrl = KEY_CTRL_J; ascii_table[DIK_K].normal = KEY_LOWERCASE_K; ascii_table[DIK_K].shift = KEY_UPPERCASE_K; ascii_table[DIK_K].left_alt = KEY_ALT_K; ascii_table[DIK_K].right_alt = KEY_ALT_K; ascii_table[DIK_K].ctrl = KEY_CTRL_K; ascii_table[DIK_L].normal = KEY_LOWERCASE_L; ascii_table[DIK_L].shift = KEY_UPPERCASE_L; ascii_table[DIK_L].left_alt = KEY_ALT_L; ascii_table[DIK_L].right_alt = KEY_ALT_L; ascii_table[DIK_L].ctrl = KEY_CTRL_L; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_SEMICOLON; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_SEMICOLON; ascii_table[k].shift = KEY_COLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_APOSTROPHE; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_3; break; default: k = DIK_2; break; } ascii_table[k].normal = KEY_SINGLE_QUOTE; ascii_table[k].shift = KEY_QUOTE; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; ascii_table[DIK_RETURN].normal = KEY_RETURN; ascii_table[DIK_RETURN].shift = KEY_RETURN; ascii_table[DIK_RETURN].left_alt = KEY_RETURN; ascii_table[DIK_RETURN].right_alt = KEY_RETURN; ascii_table[DIK_RETURN].ctrl = KEY_CTRL_J; ascii_table[DIK_LSHIFT].normal = -1; ascii_table[DIK_LSHIFT].shift = -1; ascii_table[DIK_LSHIFT].left_alt = -1; ascii_table[DIK_LSHIFT].right_alt = -1; ascii_table[DIK_LSHIFT].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_ITALIAN: case KEYBOARD_LAYOUT_SPANISH: k = DIK_Z; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_W; break; default: k = DIK_Y; break; } ascii_table[k].normal = KEY_LOWERCASE_Z; ascii_table[k].shift = KEY_UPPERCASE_Z; ascii_table[k].left_alt = KEY_ALT_Z; ascii_table[k].right_alt = KEY_ALT_Z; ascii_table[k].ctrl = KEY_CTRL_Z; ascii_table[DIK_X].normal = KEY_LOWERCASE_X; ascii_table[DIK_X].shift = KEY_UPPERCASE_X; ascii_table[DIK_X].left_alt = KEY_ALT_X; ascii_table[DIK_X].right_alt = KEY_ALT_X; ascii_table[DIK_X].ctrl = KEY_CTRL_X; ascii_table[DIK_C].normal = KEY_LOWERCASE_C; ascii_table[DIK_C].shift = KEY_UPPERCASE_C; ascii_table[DIK_C].left_alt = KEY_ALT_C; ascii_table[DIK_C].right_alt = KEY_ALT_C; ascii_table[DIK_C].ctrl = KEY_CTRL_C; ascii_table[DIK_V].normal = KEY_LOWERCASE_V; ascii_table[DIK_V].shift = KEY_UPPERCASE_V; ascii_table[DIK_V].left_alt = KEY_ALT_V; ascii_table[DIK_V].right_alt = KEY_ALT_V; ascii_table[DIK_V].ctrl = KEY_CTRL_V; ascii_table[DIK_B].normal = KEY_LOWERCASE_B; ascii_table[DIK_B].shift = KEY_UPPERCASE_B; ascii_table[DIK_B].left_alt = KEY_ALT_B; ascii_table[DIK_B].right_alt = KEY_ALT_B; ascii_table[DIK_B].ctrl = KEY_CTRL_B; ascii_table[DIK_N].normal = KEY_LOWERCASE_N; ascii_table[DIK_N].shift = KEY_UPPERCASE_N; ascii_table[DIK_N].left_alt = KEY_ALT_N; ascii_table[DIK_N].right_alt = KEY_ALT_N; ascii_table[DIK_N].ctrl = KEY_CTRL_N; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_SEMICOLON; break; default: k = DIK_M; break; } ascii_table[k].normal = KEY_LOWERCASE_M; ascii_table[k].shift = KEY_UPPERCASE_M; ascii_table[k].left_alt = KEY_ALT_M; ascii_table[k].right_alt = KEY_ALT_M; ascii_table[k].ctrl = KEY_CTRL_M; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_M; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_COMMA; ascii_table[k].shift = KEY_LESS; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_COMMA; break; default: k = DIK_PERIOD; break; } ascii_table[k].normal = KEY_DOT; ascii_table[k].shift = KEY_GREATER; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_SLASH; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_PERIOD; break; default: k = DIK_7; break; } ascii_table[k].normal = KEY_SLASH; ascii_table[k].shift = KEY_QUESTION; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; ascii_table[DIK_RSHIFT].normal = -1; ascii_table[DIK_RSHIFT].shift = -1; ascii_table[DIK_RSHIFT].left_alt = -1; ascii_table[DIK_RSHIFT].right_alt = -1; ascii_table[DIK_RSHIFT].ctrl = -1; ascii_table[DIK_LCONTROL].normal = -1; ascii_table[DIK_LCONTROL].shift = -1; ascii_table[DIK_LCONTROL].left_alt = -1; ascii_table[DIK_LCONTROL].right_alt = -1; ascii_table[DIK_LCONTROL].ctrl = -1; ascii_table[DIK_LMENU].normal = -1; ascii_table[DIK_LMENU].shift = -1; ascii_table[DIK_LMENU].left_alt = -1; ascii_table[DIK_LMENU].right_alt = -1; ascii_table[DIK_LMENU].ctrl = -1; ascii_table[DIK_SPACE].normal = KEY_SPACE; ascii_table[DIK_SPACE].shift = KEY_SPACE; ascii_table[DIK_SPACE].left_alt = KEY_SPACE; ascii_table[DIK_SPACE].right_alt = KEY_SPACE; ascii_table[DIK_SPACE].ctrl = KEY_SPACE; ascii_table[DIK_RMENU].normal = -1; ascii_table[DIK_RMENU].shift = -1; ascii_table[DIK_RMENU].left_alt = -1; ascii_table[DIK_RMENU].right_alt = -1; ascii_table[DIK_RMENU].ctrl = -1; ascii_table[DIK_RCONTROL].normal = -1; ascii_table[DIK_RCONTROL].shift = -1; ascii_table[DIK_RCONTROL].left_alt = -1; ascii_table[DIK_RCONTROL].right_alt = -1; ascii_table[DIK_RCONTROL].ctrl = -1; ascii_table[DIK_INSERT].normal = KEY_INSERT; ascii_table[DIK_INSERT].shift = KEY_INSERT; ascii_table[DIK_INSERT].left_alt = KEY_ALT_INSERT; ascii_table[DIK_INSERT].right_alt = KEY_ALT_INSERT; ascii_table[DIK_INSERT].ctrl = KEY_CTRL_INSERT; ascii_table[DIK_HOME].normal = KEY_HOME; ascii_table[DIK_HOME].shift = KEY_HOME; ascii_table[DIK_HOME].left_alt = KEY_ALT_HOME; ascii_table[DIK_HOME].right_alt = KEY_ALT_HOME; ascii_table[DIK_HOME].ctrl = KEY_CTRL_HOME; ascii_table[DIK_PRIOR].normal = KEY_PAGE_UP; ascii_table[DIK_PRIOR].shift = KEY_PAGE_UP; ascii_table[DIK_PRIOR].left_alt = KEY_ALT_PAGE_UP; ascii_table[DIK_PRIOR].right_alt = KEY_ALT_PAGE_UP; ascii_table[DIK_PRIOR].ctrl = KEY_CTRL_PAGE_UP; ascii_table[DIK_DELETE].normal = KEY_DELETE; ascii_table[DIK_DELETE].shift = KEY_DELETE; ascii_table[DIK_DELETE].left_alt = KEY_ALT_DELETE; ascii_table[DIK_DELETE].right_alt = KEY_ALT_DELETE; ascii_table[DIK_DELETE].ctrl = KEY_CTRL_DELETE; ascii_table[DIK_END].normal = KEY_END; ascii_table[DIK_END].shift = KEY_END; ascii_table[DIK_END].left_alt = KEY_ALT_END; ascii_table[DIK_END].right_alt = KEY_ALT_END; ascii_table[DIK_END].ctrl = KEY_CTRL_END; ascii_table[DIK_NEXT].normal = KEY_PAGE_DOWN; ascii_table[DIK_NEXT].shift = KEY_PAGE_DOWN; ascii_table[DIK_NEXT].left_alt = KEY_ALT_PAGE_DOWN; ascii_table[DIK_NEXT].right_alt = KEY_ALT_PAGE_DOWN; ascii_table[DIK_NEXT].ctrl = KEY_CTRL_PAGE_DOWN; ascii_table[DIK_UP].normal = KEY_ARROW_UP; ascii_table[DIK_UP].shift = KEY_ARROW_UP; ascii_table[DIK_UP].left_alt = KEY_ALT_ARROW_UP; ascii_table[DIK_UP].right_alt = KEY_ALT_ARROW_UP; ascii_table[DIK_UP].ctrl = KEY_CTRL_ARROW_UP; ascii_table[DIK_DOWN].normal = KEY_ARROW_DOWN; ascii_table[DIK_DOWN].shift = KEY_ARROW_DOWN; ascii_table[DIK_DOWN].left_alt = KEY_ALT_ARROW_DOWN; ascii_table[DIK_DOWN].right_alt = KEY_ALT_ARROW_DOWN; ascii_table[DIK_DOWN].ctrl = KEY_CTRL_ARROW_DOWN; ascii_table[DIK_LEFT].normal = KEY_ARROW_LEFT; ascii_table[DIK_LEFT].shift = KEY_ARROW_LEFT; ascii_table[DIK_LEFT].left_alt = KEY_ALT_ARROW_LEFT; ascii_table[DIK_LEFT].right_alt = KEY_ALT_ARROW_LEFT; ascii_table[DIK_LEFT].ctrl = KEY_CTRL_ARROW_LEFT; ascii_table[DIK_RIGHT].normal = KEY_ARROW_RIGHT; ascii_table[DIK_RIGHT].shift = KEY_ARROW_RIGHT; ascii_table[DIK_RIGHT].left_alt = KEY_ALT_ARROW_RIGHT; ascii_table[DIK_RIGHT].right_alt = KEY_ALT_ARROW_RIGHT; ascii_table[DIK_RIGHT].ctrl = KEY_CTRL_ARROW_RIGHT; ascii_table[DIK_NUMLOCK].normal = -1; ascii_table[DIK_NUMLOCK].shift = -1; ascii_table[DIK_NUMLOCK].left_alt = -1; ascii_table[DIK_NUMLOCK].right_alt = -1; ascii_table[DIK_NUMLOCK].ctrl = -1; ascii_table[DIK_DIVIDE].normal = KEY_SLASH; ascii_table[DIK_DIVIDE].shift = KEY_SLASH; ascii_table[DIK_DIVIDE].left_alt = -1; ascii_table[DIK_DIVIDE].right_alt = -1; ascii_table[DIK_DIVIDE].ctrl = 3; ascii_table[DIK_MULTIPLY].normal = KEY_ASTERISK; ascii_table[DIK_MULTIPLY].shift = KEY_ASTERISK; ascii_table[DIK_MULTIPLY].left_alt = -1; ascii_table[DIK_MULTIPLY].right_alt = -1; ascii_table[DIK_MULTIPLY].ctrl = -1; ascii_table[DIK_SUBTRACT].normal = KEY_MINUS; ascii_table[DIK_SUBTRACT].shift = KEY_MINUS; ascii_table[DIK_SUBTRACT].left_alt = -1; ascii_table[DIK_SUBTRACT].right_alt = -1; ascii_table[DIK_SUBTRACT].ctrl = -1; ascii_table[DIK_NUMPAD7].normal = KEY_HOME; ascii_table[DIK_NUMPAD7].shift = KEY_7; ascii_table[DIK_NUMPAD7].left_alt = KEY_ALT_HOME; ascii_table[DIK_NUMPAD7].right_alt = KEY_ALT_HOME; ascii_table[DIK_NUMPAD7].ctrl = KEY_CTRL_HOME; ascii_table[DIK_NUMPAD8].normal = KEY_ARROW_UP; ascii_table[DIK_NUMPAD8].shift = KEY_8; ascii_table[DIK_NUMPAD8].left_alt = KEY_ALT_ARROW_UP; ascii_table[DIK_NUMPAD8].right_alt = KEY_ALT_ARROW_UP; ascii_table[DIK_NUMPAD8].ctrl = KEY_CTRL_ARROW_UP; ascii_table[DIK_NUMPAD9].normal = KEY_PAGE_UP; ascii_table[DIK_NUMPAD9].shift = KEY_9; ascii_table[DIK_NUMPAD9].left_alt = KEY_ALT_PAGE_UP; ascii_table[DIK_NUMPAD9].right_alt = KEY_ALT_PAGE_UP; ascii_table[DIK_NUMPAD9].ctrl = KEY_CTRL_PAGE_UP; ascii_table[DIK_ADD].normal = KEY_PLUS; ascii_table[DIK_ADD].shift = KEY_PLUS; ascii_table[DIK_ADD].left_alt = -1; ascii_table[DIK_ADD].right_alt = -1; ascii_table[DIK_ADD].ctrl = -1; ascii_table[DIK_NUMPAD4].normal = KEY_ARROW_LEFT; ascii_table[DIK_NUMPAD4].shift = KEY_4; ascii_table[DIK_NUMPAD4].left_alt = KEY_ALT_ARROW_LEFT; ascii_table[DIK_NUMPAD4].right_alt = KEY_ALT_ARROW_LEFT; ascii_table[DIK_NUMPAD4].ctrl = KEY_CTRL_ARROW_LEFT; ascii_table[DIK_NUMPAD5].normal = KEY_NUMBERPAD_5; ascii_table[DIK_NUMPAD5].shift = KEY_5; ascii_table[DIK_NUMPAD5].left_alt = KEY_ALT_NUMBERPAD_5; ascii_table[DIK_NUMPAD5].right_alt = KEY_ALT_NUMBERPAD_5; ascii_table[DIK_NUMPAD5].ctrl = KEY_CTRL_NUMBERPAD_5; ascii_table[DIK_NUMPAD6].normal = KEY_ARROW_RIGHT; ascii_table[DIK_NUMPAD6].shift = KEY_6; ascii_table[DIK_NUMPAD6].left_alt = KEY_ALT_ARROW_RIGHT; ascii_table[DIK_NUMPAD6].right_alt = KEY_ALT_ARROW_RIGHT; ascii_table[DIK_NUMPAD6].ctrl = KEY_CTRL_ARROW_RIGHT; ascii_table[DIK_NUMPAD1].normal = KEY_END; ascii_table[DIK_NUMPAD1].shift = KEY_1; ascii_table[DIK_NUMPAD1].left_alt = KEY_ALT_END; ascii_table[DIK_NUMPAD1].right_alt = KEY_ALT_END; ascii_table[DIK_NUMPAD1].ctrl = KEY_CTRL_END; ascii_table[DIK_NUMPAD2].normal = KEY_ARROW_DOWN; ascii_table[DIK_NUMPAD2].shift = KEY_2; ascii_table[DIK_NUMPAD2].left_alt = KEY_ALT_ARROW_DOWN; ascii_table[DIK_NUMPAD2].right_alt = KEY_ALT_ARROW_DOWN; ascii_table[DIK_NUMPAD2].ctrl = KEY_CTRL_ARROW_DOWN; ascii_table[DIK_NUMPAD3].normal = KEY_PAGE_DOWN; ascii_table[DIK_NUMPAD3].shift = KEY_3; ascii_table[DIK_NUMPAD3].left_alt = KEY_ALT_PAGE_DOWN; ascii_table[DIK_NUMPAD3].right_alt = KEY_ALT_PAGE_DOWN; ascii_table[DIK_NUMPAD3].ctrl = KEY_CTRL_PAGE_DOWN; ascii_table[DIK_NUMPADENTER].normal = KEY_RETURN; ascii_table[DIK_NUMPADENTER].shift = KEY_RETURN; ascii_table[DIK_NUMPADENTER].left_alt = -1; ascii_table[DIK_NUMPADENTER].right_alt = -1; ascii_table[DIK_NUMPADENTER].ctrl = -1; ascii_table[DIK_NUMPAD0].normal = KEY_INSERT; ascii_table[DIK_NUMPAD0].shift = KEY_0; ascii_table[DIK_NUMPAD0].left_alt = KEY_ALT_INSERT; ascii_table[DIK_NUMPAD0].right_alt = KEY_ALT_INSERT; ascii_table[DIK_NUMPAD0].ctrl = KEY_CTRL_INSERT; ascii_table[DIK_DECIMAL].normal = KEY_DELETE; ascii_table[DIK_DECIMAL].shift = KEY_DOT; ascii_table[DIK_DECIMAL].left_alt = -1; ascii_table[DIK_DECIMAL].right_alt = KEY_ALT_DELETE; ascii_table[DIK_DECIMAL].ctrl = KEY_CTRL_DELETE; } // 0x4D0400 static void kb_map_ascii_French() { int k; kb_map_ascii_English_US(); ascii_table[DIK_GRAVE].normal = KEY_178; ascii_table[DIK_GRAVE].shift = -1; ascii_table[DIK_GRAVE].left_alt = -1; ascii_table[DIK_GRAVE].right_alt = -1; ascii_table[DIK_GRAVE].ctrl = -1; ascii_table[DIK_1].normal = KEY_AMPERSAND; ascii_table[DIK_1].shift = KEY_1; ascii_table[DIK_1].left_alt = -1; ascii_table[DIK_1].right_alt = -1; ascii_table[DIK_1].ctrl = -1; ascii_table[DIK_2].normal = KEY_233; ascii_table[DIK_2].shift = KEY_2; ascii_table[DIK_2].left_alt = -1; ascii_table[DIK_2].right_alt = KEY_152; ascii_table[DIK_2].ctrl = -1; ascii_table[DIK_3].normal = KEY_QUOTE; ascii_table[DIK_3].shift = KEY_3; ascii_table[DIK_3].left_alt = -1; ascii_table[DIK_3].right_alt = KEY_NUMBER_SIGN; ascii_table[DIK_3].ctrl = -1; ascii_table[DIK_4].normal = KEY_SINGLE_QUOTE; ascii_table[DIK_4].shift = KEY_4; ascii_table[DIK_4].left_alt = -1; ascii_table[DIK_4].right_alt = KEY_BRACE_LEFT; ascii_table[DIK_4].ctrl = -1; ascii_table[DIK_5].normal = KEY_PAREN_LEFT; ascii_table[DIK_5].shift = KEY_5; ascii_table[DIK_5].left_alt = -1; ascii_table[DIK_5].right_alt = KEY_BRACKET_LEFT; ascii_table[DIK_5].ctrl = -1; ascii_table[DIK_6].normal = KEY_150; ascii_table[DIK_6].shift = KEY_6; ascii_table[DIK_6].left_alt = -1; ascii_table[DIK_6].right_alt = KEY_166; ascii_table[DIK_6].ctrl = -1; ascii_table[DIK_7].normal = KEY_232; ascii_table[DIK_7].shift = KEY_7; ascii_table[DIK_7].left_alt = -1; ascii_table[DIK_7].right_alt = KEY_GRAVE; ascii_table[DIK_7].ctrl = -1; ascii_table[DIK_8].normal = KEY_UNDERSCORE; ascii_table[DIK_8].shift = KEY_8; ascii_table[DIK_8].left_alt = -1; ascii_table[DIK_8].right_alt = KEY_BACKSLASH; ascii_table[DIK_8].ctrl = -1; ascii_table[DIK_9].normal = KEY_231; ascii_table[DIK_9].shift = KEY_9; ascii_table[DIK_9].left_alt = -1; ascii_table[DIK_9].right_alt = KEY_136; ascii_table[DIK_9].ctrl = -1; ascii_table[DIK_0].normal = KEY_224; ascii_table[DIK_0].shift = KEY_0; ascii_table[DIK_0].left_alt = -1; ascii_table[DIK_0].right_alt = KEY_AT; ascii_table[DIK_0].ctrl = -1; ascii_table[DIK_MINUS].normal = KEY_PAREN_RIGHT; ascii_table[DIK_MINUS].shift = KEY_176; ascii_table[DIK_MINUS].left_alt = -1; ascii_table[DIK_MINUS].right_alt = KEY_BRACKET_RIGHT; ascii_table[DIK_MINUS].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_FRENCH: k = DIK_EQUALS; break; default: k = DIK_0; break; } ascii_table[k].normal = KEY_EQUAL; ascii_table[k].shift = KEY_PLUS; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = KEY_BRACE_RIGHT; ascii_table[k].ctrl = -1; ascii_table[DIK_LBRACKET].normal = KEY_136; ascii_table[DIK_LBRACKET].shift = KEY_168; ascii_table[DIK_LBRACKET].left_alt = -1; ascii_table[DIK_LBRACKET].right_alt = -1; ascii_table[DIK_LBRACKET].ctrl = -1; ascii_table[DIK_RBRACKET].normal = KEY_DOLLAR; ascii_table[DIK_RBRACKET].shift = KEY_163; ascii_table[DIK_RBRACKET].left_alt = -1; ascii_table[DIK_RBRACKET].right_alt = KEY_164; ascii_table[DIK_RBRACKET].ctrl = -1; ascii_table[DIK_APOSTROPHE].normal = KEY_249; ascii_table[DIK_APOSTROPHE].shift = KEY_PERCENT; ascii_table[DIK_APOSTROPHE].left_alt = -1; ascii_table[DIK_APOSTROPHE].right_alt = -1; ascii_table[DIK_APOSTROPHE].ctrl = -1; ascii_table[DIK_BACKSLASH].normal = KEY_ASTERISK; ascii_table[DIK_BACKSLASH].shift = KEY_181; ascii_table[DIK_BACKSLASH].left_alt = -1; ascii_table[DIK_BACKSLASH].right_alt = -1; ascii_table[DIK_BACKSLASH].ctrl = -1; ascii_table[DIK_OEM_102].normal = KEY_LESS; ascii_table[DIK_OEM_102].shift = KEY_GREATER; ascii_table[DIK_OEM_102].left_alt = -1; ascii_table[DIK_OEM_102].right_alt = -1; ascii_table[DIK_OEM_102].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_FRENCH: k = DIK_M; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_COMMA; ascii_table[k].shift = KEY_QUESTION; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_SEMICOLON; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_SEMICOLON; ascii_table[k].shift = KEY_DOT; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: // FIXME: Probably error, maps semicolon to colon on QWERTY keyboards. // Semicolon is already mapped above, so I bet it should be DIK_COLON. k = DIK_SEMICOLON; break; default: k = DIK_PERIOD; break; } ascii_table[k].normal = KEY_COLON; ascii_table[k].shift = KEY_SLASH; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; ascii_table[DIK_SLASH].normal = KEY_EXCLAMATION; ascii_table[DIK_SLASH].shift = KEY_167; ascii_table[DIK_SLASH].left_alt = -1; ascii_table[DIK_SLASH].right_alt = -1; ascii_table[DIK_SLASH].ctrl = -1; } // 0x4D0C54 static void kb_map_ascii_German() { int k; kb_map_ascii_English_US(); ascii_table[DIK_GRAVE].normal = KEY_136; ascii_table[DIK_GRAVE].shift = KEY_186; ascii_table[DIK_GRAVE].left_alt = -1; ascii_table[DIK_GRAVE].right_alt = -1; ascii_table[DIK_GRAVE].ctrl = -1; ascii_table[DIK_2].normal = KEY_2; ascii_table[DIK_2].shift = KEY_QUOTE; ascii_table[DIK_2].left_alt = -1; ascii_table[DIK_2].right_alt = KEY_178; ascii_table[DIK_2].ctrl = -1; ascii_table[DIK_3].normal = KEY_3; ascii_table[DIK_3].shift = KEY_167; ascii_table[DIK_3].left_alt = -1; ascii_table[DIK_3].right_alt = KEY_179; ascii_table[DIK_3].ctrl = -1; ascii_table[DIK_6].normal = KEY_6; ascii_table[DIK_6].shift = KEY_AMPERSAND; ascii_table[DIK_6].left_alt = -1; ascii_table[DIK_6].right_alt = -1; ascii_table[DIK_6].ctrl = -1; ascii_table[DIK_7].normal = KEY_7; ascii_table[DIK_7].shift = KEY_166; ascii_table[DIK_7].left_alt = -1; ascii_table[DIK_7].right_alt = KEY_BRACE_LEFT; ascii_table[DIK_7].ctrl = -1; ascii_table[DIK_8].normal = KEY_8; ascii_table[DIK_8].shift = KEY_PAREN_LEFT; ascii_table[DIK_8].left_alt = -1; ascii_table[DIK_8].right_alt = KEY_BRACKET_LEFT; ascii_table[DIK_8].ctrl = -1; ascii_table[DIK_9].normal = KEY_9; ascii_table[DIK_9].shift = KEY_PAREN_RIGHT; ascii_table[DIK_9].left_alt = -1; ascii_table[DIK_9].right_alt = KEY_BRACKET_RIGHT; ascii_table[DIK_9].ctrl = -1; ascii_table[DIK_0].normal = KEY_0; ascii_table[DIK_0].shift = KEY_EQUAL; ascii_table[DIK_0].left_alt = -1; ascii_table[DIK_0].right_alt = KEY_BRACE_RIGHT; ascii_table[DIK_0].ctrl = -1; ascii_table[DIK_MINUS].normal = KEY_223; ascii_table[DIK_MINUS].shift = KEY_QUESTION; ascii_table[DIK_MINUS].left_alt = -1; ascii_table[DIK_MINUS].right_alt = KEY_BACKSLASH; ascii_table[DIK_MINUS].ctrl = -1; ascii_table[DIK_EQUALS].normal = KEY_180; ascii_table[DIK_EQUALS].shift = KEY_GRAVE; ascii_table[DIK_EQUALS].left_alt = -1; ascii_table[DIK_EQUALS].right_alt = -1; ascii_table[DIK_EQUALS].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_A; break; default: k = DIK_Q; break; } ascii_table[k].normal = KEY_LOWERCASE_Q; ascii_table[k].shift = KEY_UPPERCASE_Q; ascii_table[k].left_alt = KEY_ALT_Q; ascii_table[k].right_alt = KEY_AT; ascii_table[k].ctrl = KEY_CTRL_Q; ascii_table[DIK_LBRACKET].normal = KEY_252; ascii_table[DIK_LBRACKET].shift = KEY_220; ascii_table[DIK_LBRACKET].left_alt = -1; ascii_table[DIK_LBRACKET].right_alt = -1; ascii_table[DIK_LBRACKET].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: case KEYBOARD_LAYOUT_FRENCH: k = DIK_EQUALS; break; default: k = DIK_RBRACKET; break; } ascii_table[k].normal = KEY_PLUS; ascii_table[k].shift = KEY_ASTERISK; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = KEY_152; ascii_table[k].ctrl = -1; ascii_table[DIK_SEMICOLON].normal = KEY_246; ascii_table[DIK_SEMICOLON].shift = KEY_214; ascii_table[DIK_SEMICOLON].left_alt = -1; ascii_table[DIK_SEMICOLON].right_alt = -1; ascii_table[DIK_SEMICOLON].ctrl = -1; ascii_table[DIK_APOSTROPHE].normal = KEY_228; ascii_table[DIK_APOSTROPHE].shift = KEY_196; ascii_table[DIK_APOSTROPHE].left_alt = -1; ascii_table[DIK_APOSTROPHE].right_alt = -1; ascii_table[DIK_APOSTROPHE].ctrl = -1; ascii_table[DIK_BACKSLASH].normal = KEY_NUMBER_SIGN; ascii_table[DIK_BACKSLASH].shift = KEY_SINGLE_QUOTE; ascii_table[DIK_BACKSLASH].left_alt = -1; ascii_table[DIK_BACKSLASH].right_alt = -1; ascii_table[DIK_BACKSLASH].ctrl = -1; ascii_table[DIK_OEM_102].normal = KEY_LESS; ascii_table[DIK_OEM_102].shift = KEY_GREATER; ascii_table[DIK_OEM_102].left_alt = -1; ascii_table[DIK_OEM_102].right_alt = KEY_166; ascii_table[DIK_OEM_102].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_SEMICOLON; break; default: k = DIK_M; break; } ascii_table[k].normal = KEY_LOWERCASE_M; ascii_table[k].shift = KEY_UPPERCASE_M; ascii_table[k].left_alt = KEY_ALT_M; ascii_table[k].right_alt = KEY_181; ascii_table[k].ctrl = KEY_CTRL_M; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_M; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_COMMA; ascii_table[k].shift = KEY_SEMICOLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_COMMA; break; default: k = DIK_PERIOD; break; } ascii_table[k].normal = KEY_DOT; ascii_table[k].shift = KEY_COLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_MINUS; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_6; break; default: k = DIK_SLASH; break; } ascii_table[k].normal = KEY_150; ascii_table[k].shift = KEY_151; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; ascii_table[DIK_DIVIDE].normal = KEY_247; ascii_table[DIK_DIVIDE].shift = KEY_247; ascii_table[DIK_DIVIDE].left_alt = -1; ascii_table[DIK_DIVIDE].right_alt = -1; ascii_table[DIK_DIVIDE].ctrl = -1; ascii_table[DIK_MULTIPLY].normal = KEY_215; ascii_table[DIK_MULTIPLY].shift = KEY_215; ascii_table[DIK_MULTIPLY].left_alt = -1; ascii_table[DIK_MULTIPLY].right_alt = -1; ascii_table[DIK_MULTIPLY].ctrl = -1; ascii_table[DIK_DECIMAL].normal = KEY_DELETE; ascii_table[DIK_DECIMAL].shift = KEY_COMMA; ascii_table[DIK_DECIMAL].left_alt = -1; ascii_table[DIK_DECIMAL].right_alt = KEY_ALT_DELETE; ascii_table[DIK_DECIMAL].ctrl = KEY_CTRL_DELETE; } // 0x4D1758 static void kb_map_ascii_Italian() { int k; kb_map_ascii_English_US(); ascii_table[DIK_GRAVE].normal = KEY_BACKSLASH; ascii_table[DIK_GRAVE].shift = KEY_BAR; ascii_table[DIK_GRAVE].left_alt = -1; ascii_table[DIK_GRAVE].right_alt = -1; ascii_table[DIK_GRAVE].ctrl = -1; ascii_table[DIK_OEM_102].normal = KEY_LESS; ascii_table[DIK_OEM_102].shift = KEY_GREATER; ascii_table[DIK_OEM_102].left_alt = -1; ascii_table[DIK_OEM_102].right_alt = -1; ascii_table[DIK_OEM_102].ctrl = -1; ascii_table[DIK_1].normal = KEY_1; ascii_table[DIK_1].shift = KEY_EXCLAMATION; ascii_table[DIK_1].left_alt = -1; ascii_table[DIK_1].right_alt = -1; ascii_table[DIK_1].ctrl = -1; ascii_table[DIK_2].normal = KEY_2; ascii_table[DIK_2].shift = KEY_QUOTE; ascii_table[DIK_2].left_alt = -1; ascii_table[DIK_2].right_alt = -1; ascii_table[DIK_2].ctrl = -1; ascii_table[DIK_3].normal = KEY_3; ascii_table[DIK_3].shift = KEY_163; ascii_table[DIK_3].left_alt = -1; ascii_table[DIK_3].right_alt = -1; ascii_table[DIK_3].ctrl = -1; ascii_table[DIK_6].normal = KEY_6; ascii_table[DIK_6].shift = KEY_AMPERSAND; ascii_table[DIK_6].left_alt = -1; ascii_table[DIK_6].right_alt = -1; ascii_table[DIK_6].ctrl = -1; ascii_table[DIK_7].normal = KEY_7; ascii_table[DIK_7].shift = KEY_SLASH; ascii_table[DIK_7].left_alt = -1; ascii_table[DIK_7].right_alt = -1; ascii_table[DIK_7].ctrl = -1; ascii_table[DIK_8].normal = KEY_8; ascii_table[DIK_8].shift = KEY_PAREN_LEFT; ascii_table[DIK_8].left_alt = -1; ascii_table[DIK_8].right_alt = -1; ascii_table[DIK_8].ctrl = -1; ascii_table[DIK_9].normal = KEY_9; ascii_table[DIK_9].shift = KEY_PAREN_RIGHT; ascii_table[DIK_9].left_alt = -1; ascii_table[DIK_9].right_alt = -1; ascii_table[DIK_9].ctrl = -1; ascii_table[DIK_0].normal = KEY_0; ascii_table[DIK_0].shift = KEY_EQUAL; ascii_table[DIK_0].left_alt = -1; ascii_table[DIK_0].right_alt = -1; ascii_table[DIK_0].ctrl = -1; ascii_table[DIK_MINUS].normal = KEY_SINGLE_QUOTE; ascii_table[DIK_MINUS].shift = KEY_QUESTION; ascii_table[DIK_MINUS].left_alt = -1; ascii_table[DIK_MINUS].right_alt = -1; ascii_table[DIK_MINUS].ctrl = -1; ascii_table[DIK_LBRACKET].normal = KEY_232; ascii_table[DIK_LBRACKET].shift = KEY_233; ascii_table[DIK_LBRACKET].left_alt = -1; ascii_table[DIK_LBRACKET].right_alt = KEY_BRACKET_LEFT; ascii_table[DIK_LBRACKET].ctrl = -1; ascii_table[DIK_RBRACKET].normal = KEY_PLUS; ascii_table[DIK_RBRACKET].shift = KEY_ASTERISK; ascii_table[DIK_RBRACKET].left_alt = -1; ascii_table[DIK_RBRACKET].right_alt = KEY_BRACKET_RIGHT; ascii_table[DIK_RBRACKET].ctrl = -1; ascii_table[DIK_BACKSLASH].normal = KEY_249; ascii_table[DIK_BACKSLASH].shift = KEY_167; ascii_table[DIK_BACKSLASH].left_alt = -1; ascii_table[DIK_BACKSLASH].right_alt = KEY_BRACKET_RIGHT; ascii_table[DIK_BACKSLASH].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_M; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_COMMA; ascii_table[k].shift = KEY_SEMICOLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_COMMA; break; default: k = DIK_PERIOD; break; } ascii_table[k].normal = KEY_DOT; ascii_table[k].shift = KEY_COLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_MINUS; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_6; break; default: k = DIK_SLASH; break; } ascii_table[k].normal = KEY_MINUS; ascii_table[k].shift = KEY_UNDERSCORE; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; } // 0x4D1E24 static void kb_map_ascii_Spanish() { int k; kb_map_ascii_English_US(); ascii_table[DIK_1].normal = KEY_1; ascii_table[DIK_1].shift = KEY_EXCLAMATION; ascii_table[DIK_1].left_alt = -1; ascii_table[DIK_1].right_alt = KEY_BAR; ascii_table[DIK_1].ctrl = -1; ascii_table[DIK_2].normal = KEY_2; ascii_table[DIK_2].shift = KEY_QUOTE; ascii_table[DIK_2].left_alt = -1; ascii_table[DIK_2].right_alt = KEY_AT; ascii_table[DIK_2].ctrl = -1; ascii_table[DIK_3].normal = KEY_3; ascii_table[DIK_3].shift = KEY_149; ascii_table[DIK_3].left_alt = -1; ascii_table[DIK_3].right_alt = KEY_NUMBER_SIGN; ascii_table[DIK_3].ctrl = -1; ascii_table[DIK_6].normal = KEY_6; ascii_table[DIK_6].shift = KEY_AMPERSAND; ascii_table[DIK_6].left_alt = -1; ascii_table[DIK_6].right_alt = KEY_172; ascii_table[DIK_6].ctrl = -1; ascii_table[DIK_7].normal = KEY_7; ascii_table[DIK_7].shift = KEY_SLASH; ascii_table[DIK_7].left_alt = -1; ascii_table[DIK_7].right_alt = -1; ascii_table[DIK_7].ctrl = -1; ascii_table[DIK_8].normal = KEY_8; ascii_table[DIK_8].shift = KEY_PAREN_LEFT; ascii_table[DIK_8].left_alt = -1; ascii_table[DIK_8].right_alt = -1; ascii_table[DIK_8].ctrl = -1; ascii_table[DIK_9].normal = KEY_9; ascii_table[DIK_9].shift = KEY_PAREN_RIGHT; ascii_table[DIK_9].left_alt = -1; ascii_table[DIK_9].right_alt = -1; ascii_table[DIK_9].ctrl = -1; ascii_table[DIK_0].normal = KEY_0; ascii_table[DIK_0].shift = KEY_EQUAL; ascii_table[DIK_0].left_alt = -1; ascii_table[DIK_0].right_alt = -1; ascii_table[DIK_0].ctrl = -1; ascii_table[DIK_MINUS].normal = KEY_146; ascii_table[DIK_MINUS].shift = KEY_QUESTION; ascii_table[DIK_MINUS].left_alt = -1; ascii_table[DIK_MINUS].right_alt = -1; ascii_table[DIK_MINUS].ctrl = -1; ascii_table[DIK_EQUALS].normal = KEY_161; ascii_table[DIK_EQUALS].shift = KEY_191; ascii_table[DIK_EQUALS].left_alt = -1; ascii_table[DIK_EQUALS].right_alt = -1; ascii_table[DIK_EQUALS].ctrl = -1; ascii_table[DIK_GRAVE].normal = KEY_176; ascii_table[DIK_GRAVE].shift = KEY_170; ascii_table[DIK_GRAVE].left_alt = -1; ascii_table[DIK_GRAVE].right_alt = KEY_BACKSLASH; ascii_table[DIK_GRAVE].ctrl = -1; ascii_table[DIK_LBRACKET].normal = KEY_GRAVE; ascii_table[DIK_LBRACKET].shift = KEY_CARET; ascii_table[DIK_LBRACKET].left_alt = -1; ascii_table[DIK_LBRACKET].right_alt = KEY_BRACKET_LEFT; ascii_table[DIK_LBRACKET].ctrl = -1; ascii_table[DIK_RBRACKET].normal = KEY_PLUS; ascii_table[DIK_RBRACKET].shift = KEY_ASTERISK; ascii_table[DIK_RBRACKET].left_alt = -1; ascii_table[DIK_RBRACKET].right_alt = KEY_BRACKET_RIGHT; ascii_table[DIK_RBRACKET].ctrl = -1; ascii_table[DIK_OEM_102].normal = KEY_LESS; ascii_table[DIK_OEM_102].shift = KEY_GREATER; ascii_table[DIK_OEM_102].left_alt = -1; ascii_table[DIK_OEM_102].right_alt = -1; ascii_table[DIK_OEM_102].ctrl = -1; ascii_table[DIK_SEMICOLON].normal = KEY_241; ascii_table[DIK_SEMICOLON].shift = KEY_209; ascii_table[DIK_SEMICOLON].left_alt = -1; ascii_table[DIK_SEMICOLON].right_alt = -1; ascii_table[DIK_SEMICOLON].ctrl = -1; ascii_table[DIK_APOSTROPHE].normal = KEY_168; ascii_table[DIK_APOSTROPHE].shift = KEY_180; ascii_table[DIK_APOSTROPHE].left_alt = -1; ascii_table[DIK_APOSTROPHE].right_alt = KEY_BRACE_LEFT; ascii_table[DIK_APOSTROPHE].ctrl = -1; ascii_table[DIK_BACKSLASH].normal = KEY_231; ascii_table[DIK_BACKSLASH].shift = KEY_199; ascii_table[DIK_BACKSLASH].left_alt = -1; ascii_table[DIK_BACKSLASH].right_alt = KEY_BRACE_RIGHT; ascii_table[DIK_BACKSLASH].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_M; break; default: k = DIK_COMMA; break; } ascii_table[k].normal = KEY_COMMA; ascii_table[k].shift = KEY_SEMICOLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_FRENCH: k = DIK_COMMA; break; default: k = DIK_PERIOD; break; } ascii_table[k].normal = KEY_DOT; ascii_table[k].shift = KEY_COLON; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; switch (kb_layout) { case KEYBOARD_LAYOUT_QWERTY: k = DIK_MINUS; break; case KEYBOARD_LAYOUT_FRENCH: k = DIK_6; break; default: k = DIK_SLASH; break; } ascii_table[k].normal = KEY_MINUS; ascii_table[k].shift = KEY_UNDERSCORE; ascii_table[k].left_alt = -1; ascii_table[k].right_alt = -1; ascii_table[k].ctrl = -1; } // 0x4D24F8 static void kb_init_lock_status() { if (GetKeyState(VK_CAPITAL) & 1) { kb_lock_flags |= MODIFIER_KEY_STATE_CAPS_LOCK; } if (GetKeyState(VK_NUMLOCK) & 1) { kb_lock_flags |= MODIFIER_KEY_STATE_NUM_LOCK; } if (GetKeyState(VK_SCROLL) & 1) { kb_lock_flags |= MODIFIER_KEY_STATE_SCROLL_LOCK; } } // NOTE: Inlined. // // 0x4D2540 static void kb_toggle_caps() { if ((kb_lock_flags & MODIFIER_KEY_STATE_CAPS_LOCK) != 0) { kb_lock_flags &= ~MODIFIER_KEY_STATE_CAPS_LOCK; } else { kb_lock_flags |= MODIFIER_KEY_STATE_CAPS_LOCK; } } // NOTE: Inlined. // // 0x4D255C static void kb_toggle_num() { if ((kb_lock_flags & MODIFIER_KEY_STATE_NUM_LOCK) != 0) { kb_lock_flags &= ~MODIFIER_KEY_STATE_NUM_LOCK; } else { kb_lock_flags |= MODIFIER_KEY_STATE_NUM_LOCK; } } // NOTE: Inlined. // // 0x4D2578 static void kb_toggle_scroll() { if ((kb_lock_flags & MODIFIER_KEY_STATE_SCROLL_LOCK) != 0) { kb_lock_flags &= ~MODIFIER_KEY_STATE_SCROLL_LOCK; } else { kb_lock_flags |= MODIFIER_KEY_STATE_SCROLL_LOCK; } } // Get pointer to pending key event from the queue but do not consume it. // // 0x4D2614 int kb_buffer_peek(int index, key_data_t** keyboardEventPtr) { int rc = -1; if (kb_get != kb_put) { int end; if (kb_put <= kb_get) { end = kb_put + KEY_QUEUE_SIZE - kb_get - 1; } else { end = kb_put - kb_get - 1; } if (index <= end) { int eventIndex = (kb_get + index) & (KEY_QUEUE_SIZE - 1); *keyboardEventPtr = &(kb_buffer[eventIndex]); rc = 0; } } return rc; } ================================================ FILE: src/plib/gnw/kb.h ================================================ #ifndef FALLOUT_PLIB_GNW_KB_H_ #define FALLOUT_PLIB_GNW_KB_H_ #include #define KEY_STATE_UP 0 #define KEY_STATE_DOWN 1 #define KEY_STATE_REPEAT 2 #define MODIFIER_KEY_STATE_NUM_LOCK 0x01 #define MODIFIER_KEY_STATE_CAPS_LOCK 0x02 #define MODIFIER_KEY_STATE_SCROLL_LOCK 0x04 #define KEYBOARD_EVENT_MODIFIER_CAPS_LOCK 0x0001 #define KEYBOARD_EVENT_MODIFIER_NUM_LOCK 0x0002 #define KEYBOARD_EVENT_MODIFIER_SCROLL_LOCK 0x0004 #define KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT 0x0008 #define KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT 0x0010 #define KEYBOARD_EVENT_MODIFIER_LEFT_ALT 0x0020 #define KEYBOARD_EVENT_MODIFIER_RIGHT_ALT 0x0040 #define KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL 0x0080 #define KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL 0x0100 #define KEYBOARD_EVENT_MODIFIER_ANY_SHIFT (KEYBOARD_EVENT_MODIFIER_LEFT_SHIFT | KEYBOARD_EVENT_MODIFIER_RIGHT_SHIFT) #define KEYBOARD_EVENT_MODIFIER_ANY_ALT (KEYBOARD_EVENT_MODIFIER_LEFT_ALT | KEYBOARD_EVENT_MODIFIER_RIGHT_ALT) #define KEYBOARD_EVENT_MODIFIER_ANY_CONTROL (KEYBOARD_EVENT_MODIFIER_LEFT_CONTROL | KEYBOARD_EVENT_MODIFIER_RIGHT_CONTROL) #define KEY_QUEUE_SIZE 64 typedef enum KeyboardLayout { KEYBOARD_LAYOUT_QWERTY, KEYBOARD_LAYOUT_FRENCH, KEYBOARD_LAYOUT_GERMAN, KEYBOARD_LAYOUT_ITALIAN, KEYBOARD_LAYOUT_SPANISH, } KeyboardLayout; typedef enum Key { KEY_ESCAPE = '\x1b', KEY_TAB = '\x09', KEY_BACKSPACE = '\x08', KEY_RETURN = '\r', KEY_SPACE = ' ', KEY_EXCLAMATION = '!', KEY_QUOTE = '"', KEY_NUMBER_SIGN = '#', KEY_DOLLAR = '$', KEY_PERCENT = '%', KEY_AMPERSAND = '&', KEY_SINGLE_QUOTE = '\'', KEY_PAREN_LEFT = '(', KEY_PAREN_RIGHT = ')', KEY_ASTERISK = '*', KEY_PLUS = '+', KEY_COMMA = ',', KEY_MINUS = '-', KEY_DOT = '.', KEY_SLASH = '/', KEY_0 = '0', KEY_1 = '1', KEY_2 = '2', KEY_3 = '3', KEY_4 = '4', KEY_5 = '5', KEY_6 = '6', KEY_7 = '7', KEY_8 = '8', KEY_9 = '9', KEY_COLON = ':', KEY_SEMICOLON = ';', KEY_LESS = '<', KEY_EQUAL = '=', KEY_GREATER = '>', KEY_QUESTION = '?', KEY_AT = '@', KEY_UPPERCASE_A = 'A', KEY_UPPERCASE_B = 'B', KEY_UPPERCASE_C = 'C', KEY_UPPERCASE_D = 'D', KEY_UPPERCASE_E = 'E', KEY_UPPERCASE_F = 'F', KEY_UPPERCASE_G = 'G', KEY_UPPERCASE_H = 'H', KEY_UPPERCASE_I = 'I', KEY_UPPERCASE_J = 'J', KEY_UPPERCASE_K = 'K', KEY_UPPERCASE_L = 'L', KEY_UPPERCASE_M = 'M', KEY_UPPERCASE_N = 'N', KEY_UPPERCASE_O = 'O', KEY_UPPERCASE_P = 'P', KEY_UPPERCASE_Q = 'Q', KEY_UPPERCASE_R = 'R', KEY_UPPERCASE_S = 'S', KEY_UPPERCASE_T = 'T', KEY_UPPERCASE_U = 'U', KEY_UPPERCASE_V = 'V', KEY_UPPERCASE_W = 'W', KEY_UPPERCASE_X = 'X', KEY_UPPERCASE_Y = 'Y', KEY_UPPERCASE_Z = 'Z', KEY_BRACKET_LEFT = '[', KEY_BACKSLASH = '\\', KEY_BRACKET_RIGHT = ']', KEY_CARET = '^', KEY_UNDERSCORE = '_', KEY_GRAVE = '`', KEY_LOWERCASE_A = 'a', KEY_LOWERCASE_B = 'b', KEY_LOWERCASE_C = 'c', KEY_LOWERCASE_D = 'd', KEY_LOWERCASE_E = 'e', KEY_LOWERCASE_F = 'f', KEY_LOWERCASE_G = 'g', KEY_LOWERCASE_H = 'h', KEY_LOWERCASE_I = 'i', KEY_LOWERCASE_J = 'j', KEY_LOWERCASE_K = 'k', KEY_LOWERCASE_L = 'l', KEY_LOWERCASE_M = 'm', KEY_LOWERCASE_N = 'n', KEY_LOWERCASE_O = 'o', KEY_LOWERCASE_P = 'p', KEY_LOWERCASE_Q = 'q', KEY_LOWERCASE_R = 'r', KEY_LOWERCASE_S = 's', KEY_LOWERCASE_T = 't', KEY_LOWERCASE_U = 'u', KEY_LOWERCASE_V = 'v', KEY_LOWERCASE_W = 'w', KEY_LOWERCASE_X = 'x', KEY_LOWERCASE_Y = 'y', KEY_LOWERCASE_Z = 'z', KEY_BRACE_LEFT = '{', KEY_BAR = '|', KEY_BRACE_RIGHT = '}', KEY_TILDE = '~', KEY_DEL = 127, KEY_136 = 136, KEY_146 = 146, KEY_149 = 149, KEY_150 = 150, KEY_151 = 151, KEY_152 = 152, KEY_161 = 161, KEY_163 = 163, KEY_164 = 164, KEY_166 = 166, KEY_168 = 168, KEY_167 = 167, KEY_170 = 170, KEY_172 = 172, KEY_176 = 176, KEY_178 = 178, KEY_179 = 179, KEY_180 = 180, KEY_181 = 181, KEY_186 = 186, KEY_191 = 191, KEY_196 = 196, KEY_199 = 199, KEY_209 = 209, KEY_214 = 214, KEY_215 = 215, KEY_220 = 220, KEY_223 = 223, KEY_224 = 224, KEY_228 = 228, KEY_231 = 231, KEY_232 = 232, KEY_233 = 233, KEY_241 = 241, KEY_246 = 246, KEY_247 = 247, KEY_249 = 249, KEY_252 = 252, KEY_ALT_Q = 272, KEY_ALT_W = 273, KEY_ALT_E = 274, KEY_ALT_R = 275, KEY_ALT_T = 276, KEY_ALT_Y = 277, KEY_ALT_U = 278, KEY_ALT_I = 279, KEY_ALT_O = 280, KEY_ALT_P = 281, KEY_ALT_A = 286, KEY_ALT_S = 287, KEY_ALT_D = 288, KEY_ALT_F = 289, KEY_ALT_G = 290, KEY_ALT_H = 291, KEY_ALT_J = 292, KEY_ALT_K = 293, KEY_ALT_L = 294, KEY_ALT_Z = 300, KEY_ALT_X = 301, KEY_ALT_C = 302, KEY_ALT_V = 303, KEY_ALT_B = 304, KEY_ALT_N = 305, KEY_ALT_M = 306, KEY_CTRL_Q = 17, KEY_CTRL_W = 23, KEY_CTRL_E = 5, KEY_CTRL_R = 18, KEY_CTRL_T = 20, KEY_CTRL_Y = 25, KEY_CTRL_U = 21, KEY_CTRL_I = 9, KEY_CTRL_O = 15, KEY_CTRL_P = 16, KEY_CTRL_A = 1, KEY_CTRL_S = 19, KEY_CTRL_D = 4, KEY_CTRL_F = 6, KEY_CTRL_G = 7, KEY_CTRL_H = 8, KEY_CTRL_J = 10, KEY_CTRL_K = 11, KEY_CTRL_L = 12, KEY_CTRL_Z = 26, KEY_CTRL_X = 24, KEY_CTRL_C = 3, KEY_CTRL_V = 22, KEY_CTRL_B = 2, KEY_CTRL_N = 14, KEY_CTRL_M = 13, KEY_F1 = 315, KEY_F2 = 316, KEY_F3 = 317, KEY_F4 = 318, KEY_F5 = 319, KEY_F6 = 320, KEY_F7 = 321, KEY_F8 = 322, KEY_F9 = 323, KEY_F10 = 324, KEY_F11 = 389, KEY_F12 = 390, KEY_SHIFT_F1 = 340, KEY_SHIFT_F2 = 341, KEY_SHIFT_F3 = 342, KEY_SHIFT_F4 = 343, KEY_SHIFT_F5 = 344, KEY_SHIFT_F6 = 345, KEY_SHIFT_F7 = 346, KEY_SHIFT_F8 = 347, KEY_SHIFT_F9 = 348, KEY_SHIFT_F10 = 349, KEY_SHIFT_F11 = 391, KEY_SHIFT_F12 = 392, KEY_CTRL_F1 = 350, KEY_CTRL_F2 = 351, KEY_CTRL_F3 = 352, KEY_CTRL_F4 = 353, KEY_CTRL_F5 = 354, KEY_CTRL_F6 = 355, KEY_CTRL_F7 = 356, KEY_CTRL_F8 = 357, KEY_CTRL_F9 = 358, KEY_CTRL_F10 = 359, KEY_CTRL_F11 = 393, KEY_CTRL_F12 = 394, KEY_ALT_F1 = 360, KEY_ALT_F2 = 361, KEY_ALT_F3 = 362, KEY_ALT_F4 = 363, KEY_ALT_F5 = 364, KEY_ALT_F6 = 365, KEY_ALT_F7 = 366, KEY_ALT_F8 = 367, KEY_ALT_F9 = 368, KEY_ALT_F10 = 369, KEY_ALT_F11 = 395, KEY_ALT_F12 = 396, KEY_HOME = 327, KEY_CTRL_HOME = 375, KEY_ALT_HOME = 407, KEY_PAGE_UP = 329, KEY_CTRL_PAGE_UP = 388, KEY_ALT_PAGE_UP = 409, KEY_INSERT = 338, KEY_CTRL_INSERT = 402, KEY_ALT_INSERT = 418, KEY_DELETE = 339, KEY_CTRL_DELETE = 403, KEY_ALT_DELETE = 419, KEY_END = 335, KEY_CTRL_END = 373, KEY_ALT_END = 415, KEY_PAGE_DOWN = 337, KEY_ALT_PAGE_DOWN = 417, KEY_CTRL_PAGE_DOWN = 374, KEY_ARROW_UP = 328, KEY_CTRL_ARROW_UP = 397, KEY_ALT_ARROW_UP = 408, KEY_ARROW_DOWN = 336, KEY_CTRL_ARROW_DOWN = 401, KEY_ALT_ARROW_DOWN = 416, KEY_ARROW_LEFT = 331, KEY_CTRL_ARROW_LEFT = 371, KEY_ALT_ARROW_LEFT = 411, KEY_ARROW_RIGHT = 333, KEY_CTRL_ARROW_RIGHT = 372, KEY_ALT_ARROW_RIGHT = 413, KEY_CTRL_BACKSLASH = 192, KEY_NUMBERPAD_5 = 332, KEY_CTRL_NUMBERPAD_5 = 399, KEY_ALT_NUMBERPAD_5 = 9999, KEY_FIRST_INPUT_CHARACTER = KEY_SPACE, KEY_LAST_INPUT_CHARACTER = KEY_LOWERCASE_Z, } Key; unsigned char keys[256]; int kb_layout; unsigned char keynumpress; int GNW_kb_set(); void GNW_kb_restore(); void kb_wait(); void kb_clear(); int kb_getch(); void kb_disable(); void kb_enable(); bool kb_is_disabled(); void kb_disable_numpad(); void kb_enable_numpad(); bool kb_numpad_is_disabled(); void kb_disable_numlock(); void kb_enable_numlock(); bool kb_numlock_is_disabled(); void kb_set_layout(int layout); int kb_get_layout(); int kb_ascii_to_scan(int ascii); unsigned int kb_elapsed_time(); void kb_reset_elapsed_time(); void kb_simulate_key(int scan_code); #endif /* FALLOUT_PLIB_GNW_KB_H_ */ ================================================ FILE: src/plib/gnw/memory.c ================================================ #include "plib/gnw/memory.h" #include #include #include #include "plib/gnw/debug.h" #include "plib/gnw/gnw.h" // A special value that denotes a beginning of a memory block data. #define MEMORY_BLOCK_HEADER_GUARD 0xFEEDFACE // A special value that denotes an ending of a memory block data. #define MEMORY_BLOCK_FOOTER_GUARD 0xBEEFCAFE // A header of a memory block. typedef struct MemoryBlockHeader { // Size of the memory block including header and footer. size_t size; // See [MEMORY_BLOCK_HEADER_GUARD]. int guard; } MemoryBlockHeader; // A footer of a memory block. typedef struct MemoryBlockFooter { // See [MEMORY_BLOCK_FOOTER_GUARD]. int guard; } MemoryBlockFooter; static void* my_malloc(size_t size); static void* my_realloc(void* ptr, size_t size); static void my_free(void* ptr); static void* mem_prep_block(void* block, size_t size); static void mem_check_block(void* block); // 0x51DED0 static MallocProc* p_malloc = my_malloc; // 0x51DED4 static ReallocProc* p_realloc = my_realloc; // 0x51DED8 static FreeProc* p_free = my_free; // 0x51DEDC static int num_blocks = 0; // 0x51DEE0 static int max_blocks = 0; // 0x51DEE4 static size_t mem_allocated = 0; // 0x51DEE8 static size_t max_allocated = 0; // 0x4C5A80 char* mem_strdup(const char* string) { char* copy = NULL; if (string != NULL) { copy = (char*)p_malloc(strlen(string) + 1); strcpy(copy, string); } return copy; } // 0x4C5AD0 void* mem_malloc(size_t size) { return p_malloc(size); } // 0x4C5AD8 static void* my_malloc(size_t size) { void* ptr = NULL; if (size != 0) { size += sizeof(MemoryBlockHeader) + sizeof(MemoryBlockFooter); unsigned char* block = (unsigned char*)malloc(size); if (block != NULL) { // NOTE: Uninline. ptr = mem_prep_block(block, size); num_blocks++; if (num_blocks > max_blocks) { max_blocks = num_blocks; } mem_allocated += size; if (mem_allocated > max_allocated) { max_allocated = mem_allocated; } } } return ptr; } // 0x4C5B50 void* mem_realloc(void* ptr, size_t size) { return p_realloc(ptr, size); } // 0x4C5B58 static void* my_realloc(void* ptr, size_t size) { if (ptr != NULL) { unsigned char* block = (unsigned char*)ptr - sizeof(MemoryBlockHeader); MemoryBlockHeader* header = (MemoryBlockHeader*)block; size_t oldSize = header->size; mem_allocated -= oldSize; mem_check_block(block); if (size != 0) { size += sizeof(MemoryBlockHeader) + sizeof(MemoryBlockFooter); } unsigned char* newBlock = (unsigned char*)realloc(block, size); if (newBlock != NULL) { mem_allocated += size; if (mem_allocated > max_allocated) { max_allocated = mem_allocated; } // NOTE: Uninline. ptr = mem_prep_block(newBlock, size); } else { if (size != 0) { mem_allocated += oldSize; debug_printf("%s,%u: ", __FILE__, __LINE__); // "Memory.c", 195 debug_printf("Realloc failure.\n"); } else { num_blocks--; } ptr = NULL; } } else { ptr = p_malloc(size); } return ptr; } // 0x4C5C24 void mem_free(void* ptr) { p_free(ptr); } // 0x4C5C2C static void my_free(void* ptr) { if (ptr != NULL) { void* block = (unsigned char*)ptr - sizeof(MemoryBlockHeader); MemoryBlockHeader* header = (MemoryBlockHeader*)block; mem_check_block(block); mem_allocated -= header->size; num_blocks--; free(block); } } // NOTE: Not used. // // 0x4C5C5C void mem_check() { if (p_malloc == my_malloc) { debug_printf("Current memory allocated: %6d blocks, %9u bytes total\n", num_blocks, mem_allocated); debug_printf("Max memory allocated: %6d blocks, %9u bytes total\n", max_blocks, max_allocated); } } // NOTE: Unused. // // 0x4C5CA8 void mem_register_func(MallocProc* mallocFunc, ReallocProc* reallocFunc, FreeProc* freeFunc) { if (!GNW_win_init_flag) { p_malloc = mallocFunc; p_realloc = reallocFunc; p_free = freeFunc; } } // NOTE: Inlined. // // 0x4C5CC4 static void* mem_prep_block(void* block, size_t size) { MemoryBlockHeader* header; MemoryBlockFooter* footer; header = (MemoryBlockHeader*)block; header->guard = MEMORY_BLOCK_HEADER_GUARD; header->size = size; footer = (MemoryBlockFooter*)((unsigned char*)block + size - sizeof(*footer)); footer->guard = MEMORY_BLOCK_FOOTER_GUARD; return (unsigned char*)block + sizeof(*header); } // Validates integrity of the memory block. // // [block] is a pointer to the the memory block itself, not it's data. // // 0x4C5CE4 static void mem_check_block(void* block) { MemoryBlockHeader* header = (MemoryBlockHeader*)block; if (header->guard != MEMORY_BLOCK_HEADER_GUARD) { debug_printf("Memory header stomped.\n"); } MemoryBlockFooter* footer = (MemoryBlockFooter*)((unsigned char*)block + header->size - sizeof(MemoryBlockFooter)); if (footer->guard != MEMORY_BLOCK_FOOTER_GUARD) { debug_printf("Memory footer stomped.\n"); } } ================================================ FILE: src/plib/gnw/memory.h ================================================ #ifndef FALLOUT_PLIB_GNW_MEMORY_H_ #define FALLOUT_PLIB_GNW_MEMORY_H_ #include "memory_defs.h" char* mem_strdup(const char* string); void* mem_malloc(size_t size); void* mem_realloc(void* ptr, size_t size); void mem_free(void* ptr); void mem_check(); void mem_register_func(MallocProc* mallocFunc, ReallocProc* reallocFunc, FreeProc* freeFunc); #endif /* FALLOUT_PLIB_GNW_MEMORY_H_ */ ================================================ FILE: src/plib/gnw/mouse.c ================================================ #include "plib/gnw/mouse.h" #include "plib/color/color.h" #include "plib/gnw/dxinput.h" #include "plib/gnw/gnw.h" #include "plib/gnw/input.h" #include "plib/gnw/memory.h" #include "plib/gnw/svga.h" #include "plib/gnw/vcr.h" static void mouse_colorize(); static void mouse_anim(); static void mouse_clip(); // The default mouse cursor buffer. // // Initially it contains color codes, which will be replaced at startup // according to loaded palette. // // Available color codes: // - 0: transparent // - 1: white // - 15: black // // 0x51E250 static unsigned char or_mask[MOUSE_DEFAULT_CURSOR_SIZE] = { // clang-format off 1, 1, 1, 1, 1, 1, 1, 0, 1, 15, 15, 15, 15, 15, 1, 0, 1, 15, 15, 15, 15, 1, 1, 0, 1, 15, 15, 15, 15, 1, 1, 0, 1, 15, 15, 15, 15, 15, 1, 1, 1, 15, 1, 1, 15, 15, 15, 1, 1, 1, 1, 1, 1, 15, 15, 1, 0, 0, 0, 0, 1, 1, 1, 1, // clang-format on }; // 0x51E290 static int mouse_idling = 0; // 0x51E294 static unsigned char* mouse_buf = NULL; // 0x51E298 static unsigned char* mouse_shape = NULL; // 0x51E29C static unsigned char* mouse_fptr = NULL; // 0x51E2A0 static double mouse_sensitivity = 1.0; // 0x51E2AC static int last_buttons = 0; // 0x6AC790 static bool mouse_is_hidden; // 0x6AC794 static int raw_x; // 0x6AC798 static int mouse_length; // 0x6AC79C static int raw_y; // 0x6AC7A0 static int raw_buttons; // 0x6AC7A4 static int mouse_y; // 0x6AC7A8 static int mouse_x; // 0x6AC7AC static bool mouse_disabled; // 0x6AC7B0 static int mouse_buttons; // 0x6AC7B4 static unsigned int mouse_speed; // 0x6AC7B8 static int mouse_curr_frame; // 0x6AC7BC static bool have_mouse; // 0x6AC7C0 static int mouse_full; // 0x6AC7C4 static int mouse_width; // 0x6AC7C8 static int mouse_num_frames; // 0x6AC7CC static int mouse_hoty; // 0x6AC7D0 static int mouse_hotx; // 0x6AC7D4 static unsigned int mouse_idle_start_time; // 0x6AC7D8 ScreenTransBlitFunc* mouse_blit_trans; // 0x6AC7DC ScreenBlitFunc* mouse_blit; // 0x6AC7E0 static char mouse_trans; // 0x4C9F40 int GNW_mouse_init() { have_mouse = false; mouse_disabled = false; mouse_is_hidden = true; mouse_colorize(); if (mouse_set_shape(NULL, 0, 0, 0, 0, 0, 0) == -1) { return -1; } if (!dxinput_acquire_mouse()) { return -1; } have_mouse = true; mouse_x = scr_size.lrx / 2; mouse_y = scr_size.lry / 2; raw_x = scr_size.lrx / 2; raw_y = scr_size.lry / 2; mouse_idle_start_time = get_time(); return 0; } // 0x4C9FD8 void GNW_mouse_exit() { dxinput_unacquire_mouse(); if (mouse_buf != NULL) { mem_free(mouse_buf); mouse_buf = NULL; } if (mouse_fptr != NULL) { remove_bk_process(mouse_anim); mouse_fptr = NULL; } } // 0x4CA01C static void mouse_colorize() { for (int index = 0; index < 64; index++) { switch (or_mask[index]) { case 0: or_mask[index] = colorTable[0]; break; case 1: or_mask[index] = colorTable[8456]; break; case 15: or_mask[index] = colorTable[32767]; break; } } } // NOTE: Unused. // // 0x4CA064 void mouse_get_shape(unsigned char** buf, int* width, int* length, int* full, int* hotx, int* hoty, char* trans) { *buf = mouse_shape; *width = mouse_width; *length = mouse_length; *full = mouse_full; *hotx = mouse_hotx; *hoty = mouse_hoty; *trans = mouse_trans; } // 0x4CA0AC int mouse_set_shape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans) { Rect rect; unsigned char* v9; int v11, v12; int v7, v8; v7 = hotx; v8 = hoty; v9 = buf; if (buf == NULL) { // NOTE: Original code looks tail recursion optimization. return mouse_set_shape(or_mask, MOUSE_DEFAULT_CURSOR_WIDTH, MOUSE_DEFAULT_CURSOR_HEIGHT, MOUSE_DEFAULT_CURSOR_WIDTH, 1, 1, colorTable[0]); } bool cursorWasHidden = mouse_is_hidden; if (!mouse_is_hidden && have_mouse) { mouse_is_hidden = true; mouse_get_rect(&rect); win_refresh_all(&rect); } if (width != mouse_width || length != mouse_length) { unsigned char* buf = (unsigned char*)mem_malloc(width * length); if (buf == NULL) { if (!cursorWasHidden) { mouse_show(); } return -1; } if (mouse_buf != NULL) { mem_free(mouse_buf); } mouse_buf = buf; } mouse_width = width; mouse_length = length; mouse_full = full; mouse_shape = v9; mouse_trans = trans; if (mouse_fptr) { remove_bk_process(mouse_anim); mouse_fptr = NULL; } v11 = mouse_hotx - v7; mouse_hotx = v7; mouse_x += v11; v12 = mouse_hoty - v8; mouse_hoty = v8; mouse_y += v12; mouse_clip(); if (!cursorWasHidden) { mouse_show(); } raw_x = mouse_x; raw_y = mouse_y; return 0; } // NOTE: Unused. // // 0x4CA20 int mouse_get_anim(unsigned char** frames, int* num_frames, int* width, int* length, int* hotx, int* hoty, char* trans, int* speed) { if (mouse_fptr == NULL) { return -1; } *frames = mouse_fptr; *num_frames = mouse_num_frames; *width = mouse_width; *length = mouse_length; *hotx = mouse_hotx; *hoty = mouse_hoty; *trans = mouse_trans; *speed = mouse_speed; return 0; } // NOTE: Unused. // // 0x4CA26C int mouse_set_anim_frames(unsigned char* frames, int num_frames, int start_frame, int width, int length, int hotx, int hoty, char trans, int speed) { if (mouse_set_shape(frames + start_frame * width * length, width, length, width, hotx, hoty, trans) == -1) { return -1; } mouse_fptr = frames; mouse_num_frames = num_frames; mouse_curr_frame = start_frame; mouse_speed = speed; add_bk_process(mouse_anim); return 0; } // 0x4CA2D0 static void mouse_anim() { // 0x51E2A8 static unsigned int ticker = 0; if (elapsed_time(ticker) >= mouse_speed) { ticker = get_time(); if (++mouse_curr_frame == mouse_num_frames) { mouse_curr_frame = 0; } mouse_shape = mouse_width * mouse_curr_frame * mouse_length + mouse_fptr; if (!mouse_is_hidden) { mouse_show(); } } } // 0x4CA34C void mouse_show() { int i; unsigned char* v2; int v7, v8; int v9, v10; int v4; unsigned char v6; int v3; v2 = mouse_buf; if (have_mouse) { if (!mouse_blit_trans || !mouse_is_hidden) { win_get_mouse_buf(mouse_buf); v2 = mouse_buf; v3 = 0; for (i = 0; i < mouse_length; i++) { for (v4 = 0; v4 < mouse_width; v4++) { v6 = mouse_shape[i * mouse_full + v4]; if (v6 != mouse_trans) { v2[v3] = v6; } v3++; } } } if (mouse_x >= scr_size.ulx) { if (mouse_width + mouse_x - 1 <= scr_size.lrx) { v8 = mouse_width; v7 = 0; } else { v7 = 0; v8 = scr_size.lrx - mouse_x + 1; } } else { v7 = scr_size.ulx - mouse_x; v8 = mouse_width - (scr_size.ulx - mouse_x); } if (mouse_y >= scr_size.uly) { if (mouse_length + mouse_y - 1 <= scr_size.lry) { v9 = 0; v10 = mouse_length; } else { v9 = 0; v10 = scr_size.lry - mouse_y + 1; } } else { v9 = scr_size.uly - mouse_y; v10 = mouse_length - (scr_size.uly - mouse_y); } mouse_buf = v2; if (mouse_blit_trans && mouse_is_hidden) { mouse_blit_trans(mouse_shape, mouse_full, mouse_length, v7, v9, v8, v10, v7 + mouse_x, v9 + mouse_y, mouse_trans); } else { mouse_blit(mouse_buf, mouse_width, mouse_length, v7, v9, v8, v10, v7 + mouse_x, v9 + mouse_y); } v2 = mouse_buf; mouse_is_hidden = false; } mouse_buf = v2; } // 0x4CA534 void mouse_hide() { Rect rect; if (have_mouse) { if (!mouse_is_hidden) { rect.ulx = mouse_x; rect.uly = mouse_y; rect.lrx = mouse_x + mouse_width - 1; rect.lry = mouse_y + mouse_length - 1; mouse_is_hidden = true; win_refresh_all(&rect); } } } // 0x4CA59C void mouse_info() { if (!have_mouse) { return; } if (mouse_is_hidden) { return; } if (mouse_disabled) { return; } int x; int y; int buttons = 0; dxinput_mouse_state mouseData; if (dxinput_get_mouse_state(&mouseData)) { x = mouseData.delta_x; y = mouseData.delta_y; if (mouseData.left_button == 1) { buttons |= MOUSE_STATE_LEFT_BUTTON_DOWN; } if (mouseData.right_button == 1) { buttons |= MOUSE_STATE_RIGHT_BUTTON_DOWN; } } else { x = 0; y = 0; } // Adjust for mouse senstivity. x = (int)(x * mouse_sensitivity); y = (int)(y * mouse_sensitivity); if (vcr_state == VCR_STATE_PLAYING) { if (((vcr_terminate_flags & VCR_TERMINATE_ON_MOUSE_PRESS) != 0 && buttons != 0) || ((vcr_terminate_flags & VCR_TERMINATE_ON_MOUSE_MOVE) != 0 && (x != 0 || y != 0))) { vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_TERMINATED; vcr_stop(); return; } x = 0; y = 0; buttons = last_buttons; } mouse_simulate_input(x, y, buttons); } // 0x4CA698 void mouse_simulate_input(int delta_x, int delta_y, int buttons) { // 0x6AC7E4 static unsigned int right_time; // 0x6AC7E8 static unsigned int left_time; // 0x6AC7EC static int old; if (!have_mouse || mouse_is_hidden) { return; } if (delta_x || delta_y || buttons != last_buttons) { if (vcr_state == 0) { if (vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) { vcr_dump_buffer(); } VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]); vcrEntry->type = VCR_ENTRY_TYPE_MOUSE_EVENT; vcrEntry->time = vcr_time; vcrEntry->counter = vcr_counter; vcrEntry->mouseEvent.dx = delta_x; vcrEntry->mouseEvent.dy = delta_y; vcrEntry->mouseEvent.buttons = buttons; vcr_buffer_index++; } } else { if (last_buttons == 0) { if (!mouse_idling) { mouse_idle_start_time = get_time(); mouse_idling = 1; } last_buttons = 0; raw_buttons = 0; mouse_buttons = 0; return; } } mouse_idling = 0; last_buttons = buttons; old = mouse_buttons; mouse_buttons = 0; if ((old & MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT) != 0) { if ((buttons & 0x01) != 0) { mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_REPEAT; if (elapsed_time(left_time) > BUTTON_REPEAT_TIME) { mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN; left_time = get_time(); } } else { mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_UP; } } else { if ((buttons & 0x01) != 0) { mouse_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN; left_time = get_time(); } } if ((old & MOUSE_EVENT_RIGHT_BUTTON_DOWN_REPEAT) != 0) { if ((buttons & 0x02) != 0) { mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_REPEAT; if (elapsed_time(right_time) > BUTTON_REPEAT_TIME) { mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN; right_time = get_time(); } } else { mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_UP; } } else { if (buttons & 0x02) { mouse_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN; right_time = get_time(); } } raw_buttons = mouse_buttons; if (delta_x != 0 || delta_y != 0) { Rect mouseRect; mouseRect.ulx = mouse_x; mouseRect.uly = mouse_y; mouseRect.lrx = mouse_width + mouse_x - 1; mouseRect.lry = mouse_length + mouse_y - 1; mouse_x += delta_x; mouse_y += delta_y; mouse_clip(); win_refresh_all(&mouseRect); mouse_show(); raw_x = mouse_x; raw_y = mouse_y; } } // 0x4CA8C8 bool mouse_in(int left, int top, int right, int bottom) { if (!have_mouse) { return false; } return mouse_length + mouse_y > top && right >= mouse_x && mouse_width + mouse_x > left && bottom >= mouse_y; } // 0x4CA934 bool mouse_click_in(int left, int top, int right, int bottom) { if (!have_mouse) { return false; } return mouse_hoty + mouse_y >= top && mouse_hotx + mouse_x <= right && mouse_hotx + mouse_x >= left && mouse_hoty + mouse_y <= bottom; } // 0x4CA9A0 void mouse_get_rect(Rect* rect) { rect->ulx = mouse_x; rect->uly = mouse_y; rect->lrx = mouse_width + mouse_x - 1; rect->lry = mouse_length + mouse_y - 1; } // 0x4CA9DC void mouse_get_position(int* xPtr, int* yPtr) { *xPtr = mouse_hotx + mouse_x; *yPtr = mouse_hoty + mouse_y; } // 0x4CAA04 void mouse_set_position(int a1, int a2) { mouse_x = a1 - mouse_hotx; mouse_y = a2 - mouse_hoty; raw_y = a2 - mouse_hoty; raw_x = a1 - mouse_hotx; mouse_clip(); } // 0x4CAA38 static void mouse_clip() { if (mouse_hotx + mouse_x < scr_size.ulx) { mouse_x = scr_size.ulx - mouse_hotx; } else if (mouse_hotx + mouse_x > scr_size.lrx) { mouse_x = scr_size.lrx - mouse_hotx; } if (mouse_hoty + mouse_y < scr_size.uly) { mouse_y = scr_size.uly - mouse_hoty; } else if (mouse_hoty + mouse_y > scr_size.lry) { mouse_y = scr_size.lry - mouse_hoty; } } // 0x4CAAA0 int mouse_get_buttons() { return mouse_buttons; } // 0x4CAAA8 bool mouse_hidden() { return mouse_is_hidden; } // NOTE: Unused. // // 0x4CAAB0 void mouse_get_hotspot(int* hotx, int* hoty) { *hotx = mouse_hotx; *hoty = mouse_hoty; } // NOTE: Unused. // // 0x4CAAC4 void mouse_set_hotspot(int hotx, int hoty) { bool mh; mh = mouse_is_hidden; if (!mouse_is_hidden) { mouse_hide(); } mouse_x += mouse_hotx - hotx; mouse_y += mouse_hoty - hoty; mouse_hotx = hotx; mouse_hoty = hoty; if (mh) { mouse_show(); } } // NOTE: Unused. // // 0x4CAB54 bool mouse_query_exist() { return have_mouse; } // 0x4CAB5C void mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons) { dxinput_mouse_state mouseData; if (!dxinput_get_mouse_state(&mouseData)) { mouseData.delta_x = 0; mouseData.delta_y = 0; mouseData.left_button = (mouse_buttons & MOUSE_EVENT_LEFT_BUTTON_DOWN) != 0; mouseData.right_button = (mouse_buttons & MOUSE_EVENT_RIGHT_BUTTON_DOWN) != 0; } raw_buttons = 0; raw_x += mouseData.delta_x; raw_y += mouseData.delta_y; if (mouseData.left_button != 0) { raw_buttons |= MOUSE_EVENT_LEFT_BUTTON_DOWN; } if (mouseData.right_button != 0) { raw_buttons |= MOUSE_EVENT_RIGHT_BUTTON_DOWN; } *out_x = raw_x; *out_y = raw_y; *out_buttons = raw_buttons; } // NOTE: Unused. // // 0x4CAC1C void mouse_disable() { mouse_disabled = true; } // NOTE: Unused. // // 0x4CAC28 void mouse_enable() { mouse_disabled = false; } // NOTE: Unused. // // 0x4CAC34 bool mouse_is_disabled() { return mouse_disabled; } // 0x4CAC3C void mouse_set_sensitivity(double value) { if (value > 0 && value < 2.0) { mouse_sensitivity = value; } } // NOTE: Unused // // 0x4CAC6C double mouse_get_sensitivity() { return mouse_sensitivity; } // NOTE: Unused. // // 0x4CAC74 unsigned int mouse_elapsed_time() { if (mouse_idling) { if (have_mouse && !mouse_is_hidden && !mouse_disabled) { return elapsed_time(mouse_idle_start_time); } mouse_idling = false; } return 0; } // NOTE: Unused. // // 0x4CAC74 void mouse_reset_elapsed_time() { if (mouse_idling) { mouse_idling = false; } } ================================================ FILE: src/plib/gnw/mouse.h ================================================ #ifndef FALLOUT_PLIB_GNW_MOUSE_H_ #define FALLOUT_PLIB_GNW_MOUSE_H_ #include #include "plib/gnw/rect.h" #include "plib/gnw/svga_types.h" #define MOUSE_DEFAULT_CURSOR_WIDTH 8 #define MOUSE_DEFAULT_CURSOR_HEIGHT 8 #define MOUSE_DEFAULT_CURSOR_SIZE (MOUSE_DEFAULT_CURSOR_WIDTH * MOUSE_DEFAULT_CURSOR_HEIGHT) #define MOUSE_STATE_LEFT_BUTTON_DOWN 0x01 #define MOUSE_STATE_RIGHT_BUTTON_DOWN 0x02 #define MOUSE_EVENT_LEFT_BUTTON_DOWN 0x01 #define MOUSE_EVENT_RIGHT_BUTTON_DOWN 0x02 #define MOUSE_EVENT_LEFT_BUTTON_REPEAT 0x04 #define MOUSE_EVENT_RIGHT_BUTTON_REPEAT 0x08 #define MOUSE_EVENT_LEFT_BUTTON_UP 0x10 #define MOUSE_EVENT_RIGHT_BUTTON_UP 0x20 #define MOUSE_EVENT_ANY_BUTTON_DOWN (MOUSE_EVENT_LEFT_BUTTON_DOWN | MOUSE_EVENT_RIGHT_BUTTON_DOWN) #define MOUSE_EVENT_ANY_BUTTON_REPEAT (MOUSE_EVENT_LEFT_BUTTON_REPEAT | MOUSE_EVENT_RIGHT_BUTTON_REPEAT) #define MOUSE_EVENT_ANY_BUTTON_UP (MOUSE_EVENT_LEFT_BUTTON_UP | MOUSE_EVENT_RIGHT_BUTTON_UP) #define MOUSE_EVENT_LEFT_BUTTON_DOWN_REPEAT (MOUSE_EVENT_LEFT_BUTTON_DOWN | MOUSE_EVENT_LEFT_BUTTON_REPEAT) #define MOUSE_EVENT_RIGHT_BUTTON_DOWN_REPEAT (MOUSE_EVENT_RIGHT_BUTTON_DOWN | MOUSE_EVENT_RIGHT_BUTTON_REPEAT) #define BUTTON_REPEAT_TIME 250 extern ScreenTransBlitFunc* mouse_blit_trans; extern ScreenBlitFunc* mouse_blit; int GNW_mouse_init(); void GNW_mouse_exit(); void mouse_get_shape(unsigned char** buf, int* width, int* length, int* full, int* hotx, int* hoty, char* trans); int mouse_set_shape(unsigned char* buf, int width, int length, int full, int hotx, int hoty, char trans); int mouse_get_anim(unsigned char** frames, int* num_frames, int* width, int* length, int* hotx, int* hoty, char* trans, int* speed); int mouse_set_anim_frames(unsigned char* frames, int num_frames, int start_frame, int width, int length, int hotx, int hoty, char trans, int speed); void mouse_show(); void mouse_hide(); void mouse_info(); void mouse_simulate_input(int delta_x, int delta_y, int buttons); bool mouse_in(int left, int top, int right, int bottom); bool mouse_click_in(int left, int top, int right, int bottom); void mouse_get_rect(Rect* rect); void mouse_get_position(int* out_x, int* out_y); void mouse_set_position(int a1, int a2); int mouse_get_buttons(); bool mouse_hidden(); void mouse_get_hotspot(int* hotx, int* hoty); void mouse_set_hotspot(int hotx, int hoty); bool mouse_query_exist(); void mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons); void mouse_disable(); void mouse_enable(); bool mouse_is_disabled(); void mouse_set_sensitivity(double value); double mouse_get_sensitivity(); unsigned int mouse_elapsed_time(); void mouse_reset_elapsed_time(); #endif /* FALLOUT_PLIB_GNW_MOUSE_H_ */ ================================================ FILE: src/plib/gnw/rect.c ================================================ #include "plib/gnw/rect.h" #include #include "plib/gnw/memory.h" // 0x51DEF4 static RectPtr rlist = NULL; // 0x4C6900 void GNW_rect_exit() { RectPtr temp; while (rlist != NULL) { temp = rlist->next; mem_free(rlist); rlist = temp; } } // 0x4C6924 void rect_clip_list(RectPtr* rectListNodePtr, Rect* rect) { Rect v1; rectCopy(&v1, rect); // NOTE: Original code is slightly different. while (*rectListNodePtr != NULL) { RectPtr rectListNode = *rectListNodePtr; if (v1.lrx >= rectListNode->rect.ulx && v1.lry >= rectListNode->rect.uly && v1.ulx <= rectListNode->rect.lrx && v1.uly <= rectListNode->rect.lry) { Rect v2; rectCopy(&v2, &(rectListNode->rect)); *rectListNodePtr = rectListNode->next; rectListNode->next = rlist; rlist = rectListNode; if (v2.uly < v1.uly) { RectPtr newRectListNode = rect_malloc(); if (newRectListNode == NULL) { return; } rectCopy(&(newRectListNode->rect), &v2); newRectListNode->rect.lry = v1.uly - 1; newRectListNode->next = *rectListNodePtr; *rectListNodePtr = newRectListNode; rectListNodePtr = &(newRectListNode->next); v2.uly = v1.uly; } if (v2.lry > v1.lry) { RectPtr newRectListNode = rect_malloc(); if (newRectListNode == NULL) { return; } rectCopy(&(newRectListNode->rect), &v2); newRectListNode->rect.uly = v1.lry + 1; newRectListNode->next = *rectListNodePtr; *rectListNodePtr = newRectListNode; rectListNodePtr = &(newRectListNode->next); v2.lry = v1.lry; } if (v2.ulx < v1.ulx) { RectPtr newRectListNode = rect_malloc(); if (newRectListNode == NULL) { return; } rectCopy(&(newRectListNode->rect), &v2); newRectListNode->rect.lrx = v1.ulx - 1; newRectListNode->next = *rectListNodePtr; *rectListNodePtr = newRectListNode; rectListNodePtr = &(newRectListNode->next); } if (v2.lrx > v1.lrx) { RectPtr newRectListNode = rect_malloc(); if (newRectListNode == NULL) { return; } rectCopy(&(newRectListNode->rect), &v2); newRectListNode->rect.ulx = v1.lrx + 1; newRectListNode->next = *rectListNodePtr; *rectListNodePtr = newRectListNode; rectListNodePtr = &(newRectListNode->next); } } else { rectListNodePtr = &(rectListNode->next); } } } // 0x4C6BB8 RectPtr rect_malloc() { RectPtr temp; int i; if (rlist == NULL) { for (i = 0; i < 10; i++) { temp = (RectPtr)mem_malloc(sizeof(*temp)); if (temp == NULL) { break; } temp->next = rlist; rlist = temp; } } if (rlist == NULL) { return NULL; } temp = rlist; rlist = rlist->next; return temp; } // 0x4C6C04 void rect_free(RectPtr ptr) { ptr->next = rlist; rlist = ptr; } // Calculates a union of two source rectangles and places it into result // rectangle. // // 0x4C6C18 void rect_min_bound(const Rect* r1, const Rect* r2, Rect* min_bound) { min_bound->ulx = min(r1->ulx, r2->ulx); min_bound->uly = min(r1->uly, r2->uly); min_bound->lrx = max(r1->lrx, r2->lrx); min_bound->lry = max(r1->lry, r2->lry); } // Calculates intersection of two source rectangles and places it into third // rectangle and returns 0. If two source rectangles do not have intersection // it returns -1 and resulting rectangle is a copy of r1. // // 0x4C6C68 int rect_inside_bound(const Rect* r1, const Rect* bound, Rect* r2) { r2->ulx = r1->ulx; r2->uly = r1->uly; r2->lrx = r1->lrx; r2->lry = r1->lry; if (r1->ulx <= bound->lrx && bound->ulx <= r1->lrx && bound->lry >= r1->uly && bound->uly <= r1->lry) { if (bound->ulx > r1->ulx) { r2->ulx = bound->ulx; } if (bound->lrx < r1->lrx) { r2->lrx = bound->lrx; } if (bound->uly > r1->uly) { r2->uly = bound->uly; } if (bound->lry < r1->lry) { r2->lry = bound->lry; } return 0; } return -1; } ================================================ FILE: src/plib/gnw/rect.h ================================================ #ifndef FALLOUT_PLIB_GNW_RECT_H_ #define FALLOUT_PLIB_GNW_RECT_H_ // TODO: Remove. typedef struct Point { int x; int y; } Point; // TODO: Remove. typedef struct Size { int width; int height; } Size; typedef struct Rect { int ulx; int uly; int lrx; int lry; } Rect; // TODO: Check. typedef struct rectdata { Rect rect; struct rectdata* next; } rectdata; typedef rectdata* RectPtr; void GNW_rect_exit(); void rect_clip_list(RectPtr* rectListNodePtr, Rect* rect); RectPtr rect_malloc(); void rect_free(RectPtr ptr); void rect_min_bound(const Rect* r1, const Rect* r2, Rect* min_bound); int rect_inside_bound(const Rect* r1, const Rect* bound, Rect* r2); // TODO: Remove. static inline void rectCopy(Rect* dest, const Rect* src) { dest->ulx = src->ulx; dest->uly = src->uly; dest->lrx = src->lrx; dest->lry = src->lry; } // TODO: Remove. static inline int rectGetWidth(const Rect* rect) { return rect->lrx - rect->ulx + 1; } // TODO: Remove. static inline int rectGetHeight(const Rect* rect) { return rect->lry - rect->uly + 1; } // TODO: Remove. static inline void rectOffset(Rect* rect, int dx, int dy) { rect->ulx += dx; rect->uly += dy; rect->lrx += dx; rect->lry += dy; } #endif /* FALLOUT_PLIB_GNW_RECT_H_ */ ================================================ FILE: src/plib/gnw/svga.c ================================================ #include "plib/gnw/svga.h" #include "mmx.h" #include "plib/gnw/gnw.h" #include "plib/gnw/grbuf.h" #include "plib/gnw/mouse.h" #include "plib/gnw/winmain.h" static int GNW95_init_mode_ex(int width, int height, int bpp); static int GNW95_init_mode(int width, int height); static int ffs(int bits); // 0x51E2B0 LPDIRECTDRAW GNW95_DDObject = NULL; // 0x51E2B4 LPDIRECTDRAWSURFACE GNW95_DDPrimarySurface = NULL; // 0x51E2B8 LPDIRECTDRAWSURFACE GNW95_DDRestoreSurface = NULL; // 0x51E2BC LPDIRECTDRAWPALETTE GNW95_DDPrimaryPalette = NULL; // 0x51E2C4 UpdatePaletteFunc* update_palette_func = NULL; // 0x51E2C8 bool mmxEnabled = true; // 0x6AC7F0 unsigned short GNW95_Pal16[256]; // screen rect Rect scr_size; // 0x6ACA00 unsigned int w95gmask; // 0x6ACA04 unsigned int w95rmask; // 0x6ACA08 unsigned int w95bmask; // 0x6ACA0C int w95bshift; // 0x6ACA10 int w95rshift; // 0x6ACA14 int w95gshift; // 0x6ACA18 ScreenBlitFunc* scr_blit = GNW95_ShowRect; // 0x6ACA1C ZeroMemFunc* zero_mem = NULL; // 0x4CACD0 void mmxEnable(bool enable) { // 0x51E2CC static bool inited = false; // 0x6ACA20 static bool mmx; if (!inited) { mmx = mmxIsSupported(); inited = true; } if (mmx) { mmxEnabled = enable; } } // 0x4CAD08 int init_mode_320_200() { return GNW95_init_mode_ex(320, 200, 8); } // 0x4CAD40 int init_mode_320_400() { return GNW95_init_mode_ex(320, 400, 8); } // 0x4CAD5C int init_mode_640_480_16() { return -1; } // 0x4CAD64 int init_mode_640_480() { return GNW95_init_mode(640, 480); } // 0x4CAD94 int init_mode_640_400() { return GNW95_init_mode(640, 400); } // 0x4CADA8 int init_mode_800_600() { return GNW95_init_mode(800, 600); } // 0x4CADBC int init_mode_1024_768() { return GNW95_init_mode(1024, 768); } // 0x4CADD0 int init_mode_1280_1024() { return GNW95_init_mode(1280, 1024); } // 0x4CADE4 int init_vesa_mode(int mode, int width, int height, int half) { if (half != 0) { return -1; } return GNW95_init_mode_ex(width, height, 8); } // 0x4CADF3 int get_start_mode() { return -1; } // 0x4CADF8 void reset_mode() { } // 0x4CADFC void zero_vid_mem() { if (zero_mem) { zero_mem(); } } // 0x4CAE1C static int GNW95_init_mode_ex(int width, int height, int bpp) { if (GNW95_init_window() == -1) { return -1; } if (GNW95_init_DirectDraw(width, height, bpp) == -1) { return -1; } scr_size.ulx = 0; scr_size.uly = 0; scr_size.lrx = width - 1; scr_size.lry = height - 1; mmxEnable(true); if (bpp == 8) { mouse_blit_trans = NULL; scr_blit = GNW95_ShowRect; zero_mem = GNW95_zero_vid_mem; mouse_blit = GNW95_ShowRect; } else { zero_mem = NULL; mouse_blit = GNW95_MouseShowRect16; mouse_blit_trans = GNW95_MouseShowTransRect16; scr_blit = GNW95_ShowRect16; } return 0; } // 0x4CAECC static int GNW95_init_mode(int width, int height) { return GNW95_init_mode_ex(width, height, 8); } // 0x4CAEDC int GNW95_init_window() { if (GNW95_hwnd == NULL) { int width = GetSystemMetrics(SM_CXSCREEN); int height = GetSystemMetrics(SM_CYSCREEN); GNW95_hwnd = CreateWindowExA(WS_EX_TOPMOST, "GNW95 Class", GNW95_title, WS_POPUP | WS_VISIBLE | WS_SYSMENU, 0, 0, width, height, NULL, NULL, GNW95_hInstance, NULL); if (GNW95_hwnd == NULL) { return -1; } UpdateWindow(GNW95_hwnd); SetFocus(GNW95_hwnd); } return 0; } // calculate shift for mask // 0x4CAF50 static int ffs(int bits) { int shift = 0; if ((bits & 0xFFFF0000) != 0) { shift |= 16; bits &= 0xFFFF0000; } if ((bits & 0xFF00FF00) != 0) { shift |= 8; bits &= 0xFF00FF00; } if ((bits & 0xF0F0F0F0) != 0) { shift |= 4; bits &= 0xF0F0F0F0; } if ((bits & 0xCCCCCCCC) != 0) { shift |= 2; bits &= 0xCCCCCCCC; } if ((bits & 0xAAAAAAAA) != 0) { shift |= 1; } return shift; } // 0x4CAF9C int GNW95_init_DirectDraw(int width, int height, int bpp) { if (GNW95_DDObject != NULL) { unsigned char* palette = GNW95_GetPalette(); GNW95_reset_mode(); if (GNW95_init_DirectDraw(width, height, bpp) == -1) { return -1; } GNW95_SetPalette(palette); return 0; } if (GNW95_DirectDrawCreate(NULL, &GNW95_DDObject, NULL) != DD_OK) { return -1; } if (IDirectDraw_SetCooperativeLevel(GNW95_DDObject, GNW95_hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN) != DD_OK) { return -1; } if (IDirectDraw_SetDisplayMode(GNW95_DDObject, width, height, bpp) != DD_OK) { return -1; } DDSURFACEDESC ddsd; memset(&ddsd, 0, sizeof(DDSURFACEDESC)); ddsd.dwSize = sizeof(DDSURFACEDESC); ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; if (IDirectDraw_CreateSurface(GNW95_DDObject, &ddsd, &GNW95_DDPrimarySurface, NULL) != DD_OK) { return -1; } GNW95_DDRestoreSurface = GNW95_DDPrimarySurface; if (bpp == 8) { PALETTEENTRY pe[256]; for (int index = 0; index < 256; index++) { pe[index].peRed = index; pe[index].peGreen = index; pe[index].peBlue = index; pe[index].peFlags = 0; } if (IDirectDraw_CreatePalette(GNW95_DDObject, DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &GNW95_DDPrimaryPalette, NULL) != DD_OK) { return -1; } if (IDirectDrawSurface_SetPalette(GNW95_DDPrimarySurface, GNW95_DDPrimaryPalette) != DD_OK) { return -1; } return 0; } else { DDPIXELFORMAT ddpf; ddpf.dwSize = sizeof(DDPIXELFORMAT); if (IDirectDrawSurface_GetPixelFormat(GNW95_DDPrimarySurface, &ddpf) != DD_OK) { return -1; } w95rmask = ddpf.dwRBitMask; w95gmask = ddpf.dwGBitMask; w95bmask = ddpf.dwBBitMask; w95rshift = ffs(w95rmask) - 7; w95gshift = ffs(w95gmask) - 7; w95bshift = ffs(w95bmask) - 7; return 0; } } // 0x4CB1B0 void GNW95_reset_mode() { if (GNW95_DDObject != NULL) { IDirectDraw_RestoreDisplayMode(GNW95_DDObject); if (GNW95_DDPrimarySurface != NULL) { IDirectDrawSurface_Release(GNW95_DDPrimarySurface); GNW95_DDPrimarySurface = NULL; GNW95_DDRestoreSurface = NULL; } if (GNW95_DDPrimaryPalette != NULL) { IDirectDrawPalette_Release(GNW95_DDPrimaryPalette); GNW95_DDPrimaryPalette = NULL; } IDirectDraw_Release(GNW95_DDObject); GNW95_DDObject = NULL; } } // 0x4CB218 void GNW95_SetPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b) { PALETTEENTRY tempEntry; r <<= 2; g <<= 2; b <<= 2; if (GNW95_DDPrimaryPalette != NULL) { tempEntry.peRed = r; tempEntry.peGreen = g; tempEntry.peBlue = b; tempEntry.peFlags = PC_NOCOLLAPSE; IDirectDrawPalette_SetEntries(GNW95_DDPrimaryPalette, 0, entry, 1, &tempEntry); } else { GNW95_Pal16[entry] = ((w95rshift > 0 ? (r << w95rshift) : (r >> -w95rshift)) & w95rmask) | ((w95gshift > 0 ? (g << w95gshift) : (r >> -w95gshift)) & w95gmask) | ((w95bshift > 0 ? (b << w95bshift) : (r >> -w95bshift)) & w95bmask); win_refresh_all(&scr_size); } if (update_palette_func != NULL) { update_palette_func(); } } // 0x4CB310 void GNW95_SetPaletteEntries(unsigned char* palette, int start, int count) { if (GNW95_DDPrimaryPalette != NULL) { PALETTEENTRY entries[256]; if (count != 0) { for (int index = 0; index < count; index++) { entries[index].peRed = palette[index * 3] << 2; entries[index].peGreen = palette[index * 3 + 1] << 2; entries[index].peBlue = palette[index * 3 + 2] << 2; entries[index].peFlags = PC_NOCOLLAPSE; } } IDirectDrawPalette_SetEntries(GNW95_DDPrimaryPalette, 0, start, count, entries); } else { for (int index = start; index < start + count; index++) { unsigned short r = palette[0] << 2; unsigned short g = palette[1] << 2; unsigned short b = palette[2] << 2; palette += 3; r = w95rshift > 0 ? (r << w95rshift) : (r >> -w95rshift); r &= w95rmask; g = w95gshift > 0 ? (g << w95gshift) : (g >> -w95gshift); g &= w95gmask; b = w95bshift > 0 ? (b << w95bshift) : (b >> -w95bshift); b &= w95bmask; unsigned short rgb = r | g | b; GNW95_Pal16[index] = rgb; } win_refresh_all(&scr_size); } if (update_palette_func != NULL) { update_palette_func(); } } // 0x4CB568 void GNW95_SetPalette(unsigned char* palette) { if (GNW95_DDPrimaryPalette != NULL) { PALETTEENTRY entries[256]; for (int index = 0; index < 256; index++) { entries[index].peRed = palette[index * 3] << 2; entries[index].peGreen = palette[index * 3 + 1] << 2; entries[index].peBlue = palette[index * 3 + 2] << 2; entries[index].peFlags = PC_NOCOLLAPSE; } IDirectDrawPalette_SetEntries(GNW95_DDPrimaryPalette, 0, 0, 256, entries); } else { for (int index = 0; index < 256; index++) { unsigned short r = palette[index * 3] << 2; unsigned short g = palette[index * 3 + 1] << 2; unsigned short b = palette[index * 3 + 2] << 2; r = w95rshift > 0 ? (r << w95rshift) : (r >> -w95rshift); r &= w95rmask; g = w95gshift > 0 ? (g << w95gshift) : (g >> -w95gshift); g &= w95gmask; b = w95bshift > 0 ? (b << w95bshift) : (b >> -w95bshift); b &= w95bmask; unsigned short rgb = r | g | b; GNW95_Pal16[index] = rgb; } win_refresh_all(&scr_size); } if (update_palette_func != NULL) { update_palette_func(); } } // 0x4CB68C unsigned char* GNW95_GetPalette() { // FIXME: This buffer was supposed to be used as temporary place to store // current palette while switching video modes (changing resolution). However // the original game does not have UI to change video mode. Even if it did this // buffer it too small to hold the entire palette, which require 256 * 3 bytes. // // 0x6ACA24 static unsigned char cmap[256]; if (GNW95_DDPrimaryPalette != NULL) { PALETTEENTRY paletteEntries[256]; if (IDirectDrawPalette_GetEntries(GNW95_DDPrimaryPalette, 0, 0, 256, paletteEntries) != DD_OK) { return NULL; } for (int index = 0; index < 256; index++) { PALETTEENTRY* paletteEntry = &(paletteEntries[index]); cmap[index * 3] = paletteEntry->peRed >> 2; cmap[index * 3 + 1] = paletteEntry->peGreen >> 2; cmap[index * 3 + 2] = paletteEntry->peBlue >> 2; } return cmap; } int redShift = w95rshift + 2; int greenShift = w95gshift + 2; int blueShift = w95bshift + 2; for (int index = 0; index < 256; index++) { unsigned short rgb = GNW95_Pal16[index]; unsigned short r = redShift > 0 ? ((rgb & w95rmask) >> redShift) : ((rgb & w95rmask) << -redShift); unsigned short g = greenShift > 0 ? ((rgb & w95gmask) >> greenShift) : ((rgb & w95gmask) << -greenShift); unsigned short b = blueShift > 0 ? ((rgb & w95bmask) >> blueShift) : ((rgb & w95bmask) << -blueShift); cmap[index * 3] = (r >> 2) & 0xFF; cmap[index * 3 + 1] = (g >> 2) & 0xFF; cmap[index * 3 + 2] = (b >> 2) & 0xFF; } return cmap; } // 0x4CB850 void GNW95_ShowRect(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY) { DDSURFACEDESC ddsd; HRESULT hr; if (!GNW95_isActive) { return; } while (1) { ddsd.dwSize = sizeof(DDSURFACEDESC); hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL); if (hr == DD_OK) { break; } if (hr == DDERR_SURFACELOST) { if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) { return; } } } buf_to_buf(src + srcPitch * srcY + srcX, srcWidth, srcHeight, srcPitch, (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + destX, ddsd.lPitch); IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface); } // 0x4CB93C void GNW95_MouseShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY) { DDSURFACEDESC ddsd; HRESULT hr; if (!GNW95_isActive) { return; } while (1) { ddsd.dwSize = sizeof(ddsd); hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL); if (hr == DD_OK) { break; } if (hr == DDERR_SURFACELOST) { if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) { return; } } } unsigned char* dest = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + 2 * destX; src += srcPitch * srcY + srcX; for (int y = 0; y < srcHeight; y++) { unsigned short* destPtr = (unsigned short*)dest; unsigned char* srcPtr = src; for (int x = 0; x < srcWidth; x++) { *destPtr = GNW95_Pal16[*srcPtr]; destPtr++; srcPtr++; } dest += ddsd.lPitch; src += srcPitch; } IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface); } // 0x4CBA44 void GNW95_ShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY) { GNW95_MouseShowRect16(src, srcPitch, a3, srcX, srcY, srcWidth, srcHeight, destX, destY); } // 0x4CBAB0 void GNW95_MouseShowTransRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, unsigned char keyColor) { DDSURFACEDESC ddsd; HRESULT hr; if (!GNW95_isActive) { return; } while (1) { ddsd.dwSize = sizeof(ddsd); hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL); if (hr == DD_OK) { break; } if (hr == DDERR_SURFACELOST) { if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) { return; } } } unsigned char* dest = (unsigned char*)ddsd.lpSurface + ddsd.lPitch * destY + 2 * destX; src += srcPitch * srcY + srcX; for (int y = 0; y < srcHeight; y++) { unsigned short* destPtr = (unsigned short*)dest; unsigned char* srcPtr = src; for (int x = 0; x < srcWidth; x++) { if (*srcPtr != keyColor) { *destPtr = GNW95_Pal16[*srcPtr]; } destPtr++; srcPtr++; } dest += ddsd.lPitch; src += srcPitch; } IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface); } // Clears drawing surface. // // 0x4CBBC8 void GNW95_zero_vid_mem() { DDSURFACEDESC ddsd; HRESULT hr; unsigned char* surface; if (!GNW95_isActive) { return; } while (1) { ddsd.dwSize = sizeof(DDSURFACEDESC); hr = IDirectDrawSurface_Lock(GNW95_DDPrimarySurface, NULL, &ddsd, 1, NULL); if (hr == DD_OK) { break; } if (hr == DDERR_SURFACELOST) { if (IDirectDrawSurface_Restore(GNW95_DDRestoreSurface) != DD_OK) { return; } } } surface = (unsigned char*)ddsd.lpSurface; for (unsigned int y = 0; y < ddsd.dwHeight; y++) { memset(surface, 0, ddsd.dwWidth); surface += ddsd.lPitch; } IDirectDrawSurface_Unlock(GNW95_DDPrimarySurface, ddsd.lpSurface); } ================================================ FILE: src/plib/gnw/svga.h ================================================ #ifndef FALLOUT_PLIB_GNW_SVGA_H_ #define FALLOUT_PLIB_GNW_SVGA_H_ #include #include "plib/gnw/gnw95dx.h" #include "plib/gnw/rect.h" #include "plib/gnw/svga_types.h" extern LPDIRECTDRAW GNW95_DDObject; extern LPDIRECTDRAWSURFACE GNW95_DDPrimarySurface; extern LPDIRECTDRAWSURFACE GNW95_DDRestoreSurface; extern LPDIRECTDRAWPALETTE GNW95_DDPrimaryPalette; extern UpdatePaletteFunc* update_palette_func; extern bool mmxEnabled; extern unsigned short GNW95_Pal16[256]; extern Rect scr_size; extern unsigned int w95rmask; extern unsigned int w95gmask; extern unsigned int w95bmask; extern int w95bshift; extern int w95rshift; extern int w95gshift; extern ScreenBlitFunc* scr_blit; extern ZeroMemFunc* zero_mem; void mmxEnable(bool enable); int init_mode_320_200(); int init_mode_320_400(); int init_mode_640_480_16(); int init_mode_640_480(); int init_mode_640_400(); int init_mode_800_600(); int init_mode_1024_768(); int init_mode_1280_1024(); int init_vesa_mode(int mode, int width, int height, int half); int get_start_mode(); void reset_mode(); void zero_vid_mem(); int GNW95_init_window(); int GNW95_init_DirectDraw(int width, int height, int bpp); void GNW95_reset_mode(); void GNW95_SetPaletteEntry(int entry, unsigned char r, unsigned char g, unsigned char b); void GNW95_SetPaletteEntries(unsigned char* a1, int a2, int a3); void GNW95_SetPalette(unsigned char* palette); unsigned char* GNW95_GetPalette(); void GNW95_ShowRect(unsigned char* src, int src_pitch, int a3, int src_x, int src_y, int src_width, int src_height, int dest_x, int dest_y); void GNW95_MouseShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY); void GNW95_ShowRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY); void GNW95_MouseShowTransRect16(unsigned char* src, int srcPitch, int a3, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, unsigned char keyColor); void GNW95_zero_vid_mem(); #endif /* FALLOUT_PLIB_GNW_SVGA_H_ */ ================================================ FILE: src/plib/gnw/svga_types.h ================================================ #ifndef FALLOUT_PLIB_GNW_SVGA_TYPES_H_ #define FALLOUT_PLIB_GNW_SVGA_TYPES_H_ // NOTE: These typedefs always appear in this order in every implementation file // with extended debug info. However `mouse.c` does not have DirectX types // implying it does not include `svga.h` which does so to expose primary // DirectDraw objects. typedef void(UpdatePaletteFunc)(); typedef void(ZeroMemFunc)(); typedef void(ResetModeFunc)(); typedef int(SetModeFunc)(); typedef void(ScreenTransBlitFunc)(unsigned char* buf, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, unsigned char a10); typedef void(ScreenBlitFunc)(unsigned char* src, int src_pitch, int a3, int src_x, int src_y, int src_width, int src_height, int dest_x, int dest_y); #endif /* FALLOUT_PLIB_GNW_SVGA_TYPES_H_ */ ================================================ FILE: src/plib/gnw/text.c ================================================ #include "plib/gnw/text.h" #include #include #include #define WIN32_LEAN_AND_MEAN #include #include "plib/color/color.h" #include "plib/db/db.h" #include "plib/gnw/memory.h" // The maximum number of text fonts. #define TEXT_FONT_MAX 10 // The maximum number of font managers. #define FONT_MANAGER_MAX 10 static int load_font(int n); static void GNW_text_font(int font_num); static bool text_font_exists(int font_num, FontMgrPtr* mgr); static void GNW_text_to_buf(unsigned char* buf, const char* str, int swidth, int fullw, int color); static int GNW_text_height(); static int GNW_text_width(const char* str); static int GNW_text_char_width(int c); static int GNW_text_mono_width(const char* str); static int GNW_text_spacing(); static int GNW_text_size(const char* str); static int GNW_text_max(); // 0x51E3B0 static int curr_font_num = -1; // 0x51E3B4 static int total_managers = 0; // 0x51E3B8 text_to_buf_func* text_to_buf = NULL; // 0x51E3BC text_height_func* text_height = NULL; // 0x51E3C0 text_width_func* text_width = NULL; // 0x51E3C4 text_char_width_func* text_char_width = NULL; // 0x51E3C8 text_mono_width_func* text_mono_width = NULL; // 0x51E3CC text_spacing_func* text_spacing = NULL; // 0x51E3D0 text_size_func* text_size = NULL; // 0x51E3D4 text_max_func* text_max = NULL; // 0x6ADB08 static Font font[TEXT_FONT_MAX]; // 0x6ADBD0 static FontMgr font_managers[FONT_MANAGER_MAX]; // 0x6ADD88 static Font* curr_font; // 0x4D555C int GNW_text_init() { // 0x4D5530 static FontMgr GNW_font_mgr = { 0, 9, GNW_text_font, GNW_text_to_buf, GNW_text_height, GNW_text_width, GNW_text_char_width, GNW_text_mono_width, GNW_text_spacing, GNW_text_size, GNW_text_max, }; int i; int first_font; first_font = -1; for (i = 0; i < TEXT_FONT_MAX; i++) { if (load_font(i) == -1) { font[i].num = 0; } else { if (first_font == -1) { first_font = i; } } } if (first_font == -1) { return -1; } if (text_add_manager(&GNW_font_mgr) == -1) { return -1; } text_font(first_font); return 0; } // 0x4D55CC void GNW_text_exit() { int i; for (i = 0; i < TEXT_FONT_MAX; i++) { if (font[i].num != 0) { mem_free(font[i].info); mem_free(font[i].data); } } } // 0x4D55FC static int load_font(int n) { int rc = -1; char path[MAX_PATH]; sprintf(path, "font%d.fon", n); // NOTE: Original code is slightly different. It uses deep nesting and // unwinds everything from the point of failure. Font* textFontDescriptor = &(font[n]); textFontDescriptor->data = NULL; textFontDescriptor->info = NULL; File* stream = db_fopen(path, "rb"); int dataSize; if (stream == NULL) { goto out; } if (db_fread(textFontDescriptor, sizeof(Font), 1, stream) != 1) { goto out; } textFontDescriptor->info = (FontInfo*)mem_malloc(textFontDescriptor->num * sizeof(FontInfo)); if (textFontDescriptor->info == NULL) { goto out; } if (db_fread(textFontDescriptor->info, sizeof(FontInfo), textFontDescriptor->num, stream) != textFontDescriptor->num) { goto out; } dataSize = textFontDescriptor->height * ((textFontDescriptor->info[textFontDescriptor->num - 1].width + 7) >> 3) + textFontDescriptor->info[textFontDescriptor->num - 1].offset; textFontDescriptor->data = (unsigned char*)mem_malloc(dataSize); if (textFontDescriptor->data == NULL) { goto out; } if (db_fread(textFontDescriptor->data, 1, dataSize, stream) != dataSize) { goto out; } rc = 0; out: if (rc != 0) { if (textFontDescriptor->data != NULL) { mem_free(textFontDescriptor->data); textFontDescriptor->data = NULL; } if (textFontDescriptor->info != NULL) { mem_free(textFontDescriptor->info); textFontDescriptor->info = NULL; } } if (stream != NULL) { db_fclose(stream); } return rc; } // 0x4D5780 int text_add_manager(FontMgrPtr mgr) { int k; if (mgr == NULL) { return -1; } if (total_managers >= FONT_MANAGER_MAX) { return -1; } // Check if a font manager exists for any font in the specified range. for (k = mgr->low_font_num; k < mgr->high_font_num; k++) { FontMgrPtr mgr; if (text_font_exists(k, &mgr)) { return -1; } } memcpy(&(font_managers[total_managers]), mgr, sizeof(*mgr)); total_managers++; return 0; } // 0x4D58AC static void GNW_text_font(int font_num) { if (font_num >= TEXT_FONT_MAX) { return; } if (font[font_num].num == 0) { return; } curr_font = &(font[font_num]); } // 0x4D58D4 int text_curr() { return curr_font_num; } // 0x4D58DC void text_font(int font_num) { FontMgrPtr mgr; if (text_font_exists(font_num, &mgr)) { text_to_buf = mgr->text_to_buf; text_height = mgr->text_height; text_width = mgr->text_width; text_char_width = mgr->text_char_width; text_mono_width = mgr->text_mono_width; text_spacing = mgr->text_spacing; text_size = mgr->text_size; text_max = mgr->text_max; curr_font_num = font_num; mgr->text_font(font_num); } } // 0x4D595C static bool text_font_exists(int font_num, FontMgrPtr* mgr) { int k; for (k = 0; k < total_managers; k++) { if (font_num >= font_managers[k].low_font_num && font_num <= font_managers[k].high_font_num) { *mgr = &(font_managers[k]); return true; } } return false; } // 0x4D59B0 void GNW_text_to_buf(unsigned char* buf, const char* str, int swidth, int fullw, int color) { if ((color & FONT_SHADOW) != 0) { color &= ~FONT_SHADOW; text_to_buf(buf + fullw + 1, str, swidth, fullw, colorTable[0]); } int monospacedCharacterWidth; if ((color & FONT_MONO) != 0) { monospacedCharacterWidth = text_max(); } unsigned char* ptr = buf; while (*str != '\0') { char ch = *str++; if (ch < curr_font->num) { FontInfo* glyph = &(curr_font->info[ch & 0xFF]); unsigned char* end; if ((color & FONT_MONO) != 0) { end = ptr + monospacedCharacterWidth; ptr += (monospacedCharacterWidth - curr_font->spacing - glyph->width) / 2; } else { end = ptr + glyph->width + curr_font->spacing; } if (end - buf > swidth) { break; } unsigned char* glyphData = curr_font->data + glyph->offset; for (int y = 0; y < curr_font->height; y++) { int bits = 0x80; for (int x = 0; x < glyph->width; x++) { if (bits == 0) { bits = 0x80; glyphData++; } if ((*glyphData & bits) != 0) { *ptr = color & 0xFF; } bits >>= 1; ptr++; } glyphData++; ptr += fullw - glyph->width; } ptr = end; } } if ((color & FONT_UNDERLINE) != 0) { // TODO: Probably additional -1 present, check. int length = ptr - buf; unsigned char* underlinePtr = buf + fullw * (curr_font->height - 1); for (int pix = 0; pix < length; pix++) { *underlinePtr++ = color & 0xFF; } } } // 0x4D5B54 static int GNW_text_height() { return curr_font->height; } // 0x4D5B60 static int GNW_text_width(const char* str) { int i; int len; FontInfo* fi; len = 0; for (i = 0; str[i] != '\0'; i++) { if (str[i] < curr_font->num) { fi = &(curr_font->info[str[i]]); len += curr_font->spacing + fi->width; } } return len; } // 0x4D5BA4 static int GNW_text_char_width(int c) { return curr_font->info[c].width; } // 0x4D5BB8 static int GNW_text_mono_width(const char* str) { return text_max() * strlen(str); } // 0x4D5BD8 static int GNW_text_spacing() { return curr_font->spacing; } // 0x4D5BE4 static int GNW_text_size(const char* str) { return text_width(str) * text_height(); } // 0x4D5BF8 static int GNW_text_max() { int i; int len; FontInfo* fi; len = 0; for (i = 0; i < curr_font->num; i++) { fi = &(curr_font->info[i]); if (len < fi->width) { len = fi->width; } } return len + curr_font->spacing; } ================================================ FILE: src/plib/gnw/text.h ================================================ #ifndef FALLOUT_PLIB_GNW_TEXT_H_ #define FALLOUT_PLIB_GNW_TEXT_H_ #define FONT_SHADOW 0x10000 #define FONT_UNDERLINE 0x20000 #define FONT_MONO 0x40000 typedef void text_font_func(int font); typedef void text_to_buf_func(unsigned char* buf, const char* str, int swidth, int fullw, int color); typedef int text_height_func(); typedef int text_width_func(const char* str); // TODO: Convert type to `char`. typedef int text_char_width_func(int ch); typedef int text_mono_width_func(const char* str); typedef int text_spacing_func(); typedef int text_size_func(const char* str); typedef int text_max_func(); typedef struct FontMgr { int low_font_num; int high_font_num; text_font_func* text_font; text_to_buf_func* text_to_buf; text_height_func* text_height; text_width_func* text_width; text_char_width_func* text_char_width; text_mono_width_func* text_mono_width; text_spacing_func* text_spacing; text_size_func* text_size; text_max_func* text_max; } FontMgr; static_assert(sizeof(FontMgr) == 44, "wrong size"); typedef FontMgr* FontMgrPtr; typedef struct FontInfo { // The width of the glyph in pixels. int width; // Data offset into [Font.data]. int offset; } FontInfo; typedef struct Font { // The number of glyphs in the font. int num; // The height of the font. int height; // Horizontal spacing between characters in pixels. int spacing; FontInfo* info; unsigned char* data; } Font; extern text_to_buf_func* text_to_buf; extern text_height_func* text_height; extern text_width_func* text_width; extern text_char_width_func* text_char_width; extern text_mono_width_func* text_mono_width; extern text_spacing_func* text_spacing; extern text_size_func* text_size; extern text_max_func* text_max; int GNW_text_init(); void GNW_text_exit(); int text_add_manager(FontMgrPtr mgr); int text_curr(); void text_font(int font); #endif /* FALLOUT_PLIB_GNW_TEXT_H_ */ ================================================ FILE: src/plib/gnw/vcr.c ================================================ #include "plib/gnw/vcr.h" #include #include "plib/gnw/input.h" #include "plib/gnw/memory.h" static bool vcr_create_buffer(); static bool vcr_destroy_buffer(); static bool vcr_clear_buffer(); static bool vcr_load_buffer(); // 0x51E2F0 VcrEntry* vcr_buffer = NULL; // number of entries in vcr_buffer // 0x51E2F4 int vcr_buffer_index = 0; // 0x51E2F8 unsigned int vcr_state = VCR_STATE_TURNED_OFF; // 0x51E2FC unsigned int vcr_time = 0; // 0x51E300 unsigned int vcr_counter = 0; // 0x51E304 unsigned int vcr_terminate_flags = 0; // 0x51E308 int vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_NONE; // 0x51E30C static unsigned int vcr_start_time = 0; // 0x51E310 static int vcr_registered_atexit = 0; // 0x51E314 static File* vcr_file = NULL; // 0x51E318 static int vcr_buffer_end = 0; // 0x51E31C static VcrPlaybackCompletionCallback* vcr_notify_callback = NULL; // 0x51E320 static unsigned int vcr_temp_terminate_flags = 0; // 0x51E324 static int vcr_old_layout = 0; // 0x6AD940 static VcrEntry vcr_last_play_event; // 0x4D2680 bool vcr_record(const char* fileName) { if (vcr_state != VCR_STATE_TURNED_OFF) { return false; } if (fileName == NULL) { return false; } // NOTE: Uninline. if (!vcr_create_buffer()) { return false; } vcr_file = db_fopen(fileName, "wb"); if (vcr_file == NULL) { // NOTE: Uninline. vcr_destroy_buffer(); return false; } if (vcr_registered_atexit == 0) { vcr_registered_atexit = atexit(vcr_stop); } VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]); vcrEntry->type = VCR_ENTRY_TYPE_INITIAL_STATE; vcrEntry->time = 0; vcrEntry->counter = 0; vcrEntry->initial.keyboardLayout = kb_get_layout(); while (mouse_get_buttons() != 0) { mouse_info(); } mouse_get_position(&(vcrEntry->initial.mouseX), &(vcrEntry->initial.mouseY)); vcr_counter = 1; vcr_buffer_index++; vcr_start_time = get_time(); kb_clear(); vcr_state = VCR_STATE_RECORDING; return true; } // 0x4D27EC bool vcr_play(const char* fileName, unsigned int terminationFlags, VcrPlaybackCompletionCallback* callback) { if (vcr_state != VCR_STATE_TURNED_OFF) { return false; } if (fileName == NULL) { return false; } // NOTE: Uninline. if (!vcr_create_buffer()) { return false; } vcr_file = db_fopen(fileName, "rb"); if (vcr_file == NULL) { // NOTE: Uninline. vcr_destroy_buffer(); return false; } if (!vcr_load_buffer()) { db_fclose(vcr_file); // NOTE: Uninline. vcr_destroy_buffer(); return false; } while (mouse_get_buttons() != 0) { mouse_info(); } kb_clear(); vcr_temp_terminate_flags = terminationFlags; vcr_notify_callback = callback; vcr_terminated_condition = VCR_PLAYBACK_COMPLETION_REASON_COMPLETED; vcr_terminate_flags = 0; vcr_counter = 0; vcr_time = 0; vcr_start_time = get_time(); vcr_state = VCR_STATE_PLAYING; vcr_last_play_event.time = 0; vcr_last_play_event.counter = 0; return true; } // 0x4D28F4 int vcr_stop(void) { if (vcr_state == VCR_STATE_RECORDING || vcr_state == VCR_STATE_PLAYING) { vcr_state |= VCR_STATE_STOP_REQUESTED; } kb_clear(); return 1; } // 0x4D2918 int vcr_status() { return vcr_state; } // 0x4D2930 int vcr_update() { if ((vcr_state & VCR_STATE_STOP_REQUESTED) != 0) { vcr_state &= ~VCR_STATE_STOP_REQUESTED; switch (vcr_state) { case VCR_STATE_RECORDING: vcr_dump_buffer(); db_fclose(vcr_file); vcr_file = NULL; // NOTE: Uninline. vcr_destroy_buffer(); break; case VCR_STATE_PLAYING: db_fclose(vcr_file); vcr_file = NULL; // NOTE: Uninline. vcr_destroy_buffer(); kb_set_layout(vcr_old_layout); if (vcr_notify_callback != NULL) { vcr_notify_callback(vcr_terminated_condition); } break; } vcr_state = VCR_STATE_TURNED_OFF; } switch (vcr_state) { case VCR_STATE_RECORDING: vcr_counter++; vcr_time = elapsed_time(vcr_start_time); if (vcr_buffer_index == VCR_BUFFER_CAPACITY - 1) { vcr_dump_buffer(); } break; case VCR_STATE_PLAYING: if (vcr_buffer_index < vcr_buffer_end || vcr_load_buffer()) { VcrEntry* vcrEntry = &(vcr_buffer[vcr_buffer_index]); if (vcr_last_play_event.counter < vcrEntry->counter) { if (vcrEntry->time > vcr_last_play_event.time) { unsigned int delay = vcr_last_play_event.time; delay += (vcr_counter - vcr_last_play_event.counter) * (vcrEntry->time - vcr_last_play_event.time) / (vcrEntry->counter - vcr_last_play_event.counter); while (elapsed_time(vcr_start_time) < delay) { } } } vcr_counter++; int rc = 0; while (vcr_counter >= vcr_buffer[vcr_buffer_index].counter) { vcr_time = elapsed_time(vcr_start_time); if (vcr_time > vcr_buffer[vcr_buffer_index].time + 5 || vcr_time < vcr_buffer[vcr_buffer_index].time - 5) { vcr_start_time += vcr_time - vcr_buffer[vcr_buffer_index].time; } switch (vcr_buffer[vcr_buffer_index].type) { case VCR_ENTRY_TYPE_INITIAL_STATE: vcr_state = VCR_STATE_TURNED_OFF; vcr_old_layout = kb_get_layout(); kb_set_layout(vcr_buffer[vcr_buffer_index].initial.keyboardLayout); while (mouse_get_buttons() != 0) { mouse_info(); } vcr_state = VCR_ENTRY_TYPE_INITIAL_STATE; mouse_hide(); mouse_set_position(vcr_buffer[vcr_buffer_index].initial.mouseX, vcr_buffer[vcr_buffer_index].initial.mouseY); mouse_show(); kb_clear(); vcr_terminate_flags = vcr_temp_terminate_flags; vcr_start_time = get_time(); vcr_counter = 0; break; case VCR_ENTRY_TYPE_KEYBOARD_EVENT: kb_simulate_key(vcr_buffer[vcr_buffer_index].keyboardEvent.key); break; case VCR_ENTRY_TYPE_MOUSE_EVENT: rc = 3; mouse_simulate_input(vcr_buffer[vcr_buffer_index].mouseEvent.dx, vcr_buffer[vcr_buffer_index].mouseEvent.dy, vcr_buffer[vcr_buffer_index].mouseEvent.buttons); break; } memcpy(&vcr_last_play_event, &(vcr_buffer[vcr_buffer_index]), sizeof(vcr_last_play_event)); vcr_buffer_index++; } return rc; } else { // NOTE: Uninline. vcr_stop(); } break; } return 0; } // NOTE: Inlined. // // 0x4D2C64 static bool vcr_create_buffer() { if (vcr_buffer == NULL) { vcr_buffer = (VcrEntry*)mem_malloc(sizeof(*vcr_buffer) * VCR_BUFFER_CAPACITY); if (vcr_buffer == NULL) { return false; } } // NOTE: Uninline. vcr_clear_buffer(); return true; } // NOTE: Inlined. // // 0x4D2C98 static bool vcr_destroy_buffer() { if (vcr_buffer == NULL) { return false; } // NOTE: Uninline. vcr_clear_buffer(); mem_free(vcr_buffer); vcr_buffer = NULL; return true; } // 0x4D2CD0 static bool vcr_clear_buffer() { if (vcr_buffer == NULL) { return false; } vcr_buffer_index = 0; return true; } // 0x4D2CF0 bool vcr_dump_buffer() { if (vcr_buffer == NULL) { return false; } if (vcr_file == NULL) { return false; } for (int index = 0; index < vcr_buffer_index; index++) { if (!vcr_save_record(&(vcr_buffer[index]), vcr_file)) { return false; } } // NOTE: Uninline. if (!vcr_clear_buffer()) { return false; } return true; } // 0x4D2D74 static bool vcr_load_buffer() { if (vcr_file == NULL) { return false; } // NOTE: Uninline. if (!vcr_clear_buffer()) { return false; } for (vcr_buffer_end = 0; vcr_buffer_end < VCR_BUFFER_CAPACITY; vcr_buffer_end++) { if (!vcr_load_record(&(vcr_buffer[vcr_buffer_end]), vcr_file)) { break; } } if (vcr_buffer_end == 0) { return false; } return true; } // 0x4D2E00 bool vcr_save_record(VcrEntry* vcrEntry, File* stream) { if (db_fwriteLong(stream, vcrEntry->type) == -1) return false; if (db_fwriteLong(stream, vcrEntry->time) == -1) return false; if (db_fwriteLong(stream, vcrEntry->counter) == -1) return false; switch (vcrEntry->type) { case VCR_ENTRY_TYPE_INITIAL_STATE: if (db_fwriteLong(stream, vcrEntry->initial.mouseX) == -1) return false; if (db_fwriteLong(stream, vcrEntry->initial.mouseY) == -1) return false; if (db_fwriteLong(stream, vcrEntry->initial.keyboardLayout) == -1) return false; return true; case VCR_ENTRY_TYPE_KEYBOARD_EVENT: if (db_fwriteShort(stream, vcrEntry->keyboardEvent.key) == -1) return false; return true; case VCR_ENTRY_TYPE_MOUSE_EVENT: if (db_fwriteLong(stream, vcrEntry->mouseEvent.dx) == -1) return false; if (db_fwriteLong(stream, vcrEntry->mouseEvent.dy) == -1) return false; if (db_fwriteLong(stream, vcrEntry->mouseEvent.buttons) == -1) return false; return true; } return false; } // 0x4D2EE4 bool vcr_load_record(VcrEntry* vcrEntry, File* stream) { if (db_freadLong(stream, &(vcrEntry->type)) == -1) return false; if (db_freadLong(stream, &(vcrEntry->time)) == -1) return false; if (db_freadLong(stream, &(vcrEntry->counter)) == -1) return false; switch (vcrEntry->type) { case VCR_ENTRY_TYPE_INITIAL_STATE: if (db_freadLong(stream, &(vcrEntry->initial.mouseX)) == -1) return false; if (db_freadLong(stream, &(vcrEntry->initial.mouseY)) == -1) return false; if (db_freadLong(stream, &(vcrEntry->initial.keyboardLayout)) == -1) return false; return true; case VCR_ENTRY_TYPE_KEYBOARD_EVENT: if (db_freadShort(stream, &(vcrEntry->keyboardEvent.key)) == -1) return false; return true; case VCR_ENTRY_TYPE_MOUSE_EVENT: if (db_freadLong(stream, &(vcrEntry->mouseEvent.dx)) == -1) return false; if (db_freadLong(stream, &(vcrEntry->mouseEvent.dy)) == -1) return false; if (db_freadLong(stream, &(vcrEntry->mouseEvent.buttons)) == -1) return false; return true; } return false; } ================================================ FILE: src/plib/gnw/vcr.h ================================================ #ifndef FALLOUT_PLIB_GNW_VCR_H_ #define FALLOUT_PLIB_GNW_VCR_H_ #include #include "plib/db/db.h" #define VCR_BUFFER_CAPACITY 4096 typedef enum VcrState { VCR_STATE_RECORDING, VCR_STATE_PLAYING, VCR_STATE_TURNED_OFF, } VcrState; #define VCR_STATE_STOP_REQUESTED 0x80000000 typedef enum VcrTerminationFlags { // Specifies that VCR playback should stop if any key is pressed. VCR_TERMINATE_ON_KEY_PRESS = 0x01, // Specifies that VCR playback should stop if mouse is mouved. VCR_TERMINATE_ON_MOUSE_MOVE = 0x02, // Specifies that VCR playback should stop if any mouse button is pressed. VCR_TERMINATE_ON_MOUSE_PRESS = 0x04, } VcrTerminationFlags; typedef enum VcrPlaybackCompletionReason { VCR_PLAYBACK_COMPLETION_REASON_NONE = 0, // Indicates that VCR playback completed normally. VCR_PLAYBACK_COMPLETION_REASON_COMPLETED = 1, // Indicates that VCR playback terminated according to termination flags. VCR_PLAYBACK_COMPLETION_REASON_TERMINATED = 2, } VcrPlaybackCompletionReason; typedef enum VcrEntryType { VCR_ENTRY_TYPE_NONE = 0, VCR_ENTRY_TYPE_INITIAL_STATE = 1, VCR_ENTRY_TYPE_KEYBOARD_EVENT = 2, VCR_ENTRY_TYPE_MOUSE_EVENT = 3, } VcrEntryType; typedef struct VcrEntry { unsigned int type; unsigned int time; unsigned int counter; union { struct { int mouseX; int mouseY; int keyboardLayout; } initial; struct { short key; } keyboardEvent; struct { int dx; int dy; int buttons; } mouseEvent; }; } VcrEntry; static_assert(sizeof(VcrEntry) == 24, "wrong size"); typedef void(VcrPlaybackCompletionCallback)(int reason); extern VcrEntry* vcr_buffer; extern int vcr_buffer_index; extern unsigned int vcr_state; extern unsigned int vcr_time; extern unsigned int vcr_counter; extern unsigned int vcr_terminate_flags; extern int vcr_terminated_condition; bool vcr_record(const char* fileName); bool vcr_play(const char* fileName, unsigned int terminationFlags, VcrPlaybackCompletionCallback* callback); int vcr_stop(void); int vcr_status(); int vcr_update(); bool vcr_dump_buffer(); bool vcr_save_record(VcrEntry* ptr, File* stream); bool vcr_load_record(VcrEntry* ptr, File* stream); #endif /* FALLOUT_PLIB_GNW_VCR_H_ */ ================================================ FILE: src/plib/gnw/winmain.c ================================================ #include "plib/gnw/winmain.h" #include #include "plib/gnw/doscmdln.h" #include "plib/gnw/input.h" #include "game/main.h" #include "plib/gnw/gnw.h" #include "plib/gnw/svga.h" static BOOL LoadDirectX(); static void UnloadDirectX(void); // 0x51E434 HWND GNW95_hwnd = NULL; // 0x51E438 HINSTANCE GNW95_hInstance = NULL; // 0x51E43C LPSTR GNW95_lpszCmdLine = NULL; // 0x51E440 int GNW95_nCmdShow = 0; // 0x51E444 bool GNW95_isActive = false; // GNW95MUTEX HANDLE GNW95_mutex = NULL; // 0x51E44C HMODULE GNW95_hDDrawLib = NULL; // 0x51E450 HMODULE GNW95_hDInputLib = NULL; // 0x51E454 HMODULE GNW95_hDSoundLib = NULL; // 0x6B23D0 char GNW95_title[256]; // 0x4DE700 int WINAPI WinMain(_In_ HINSTANCE hInst, _In_opt_ HINSTANCE hPrevInst, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) { DOSCmdLine args; GNW95_mutex = CreateMutexA(0, TRUE, "GNW95MUTEX"); if (GetLastError() == ERROR_SUCCESS) { ShowCursor(0); if (InitClass(hInst)) { if (InitInstance()) { if (LoadDirectX()) { GNW95_hInstance = hInst; GNW95_lpszCmdLine = lpCmdLine; GNW95_nCmdShow = nCmdShow; DOSCmdLineInit(&args); if (DOSCmdLineCreate(&args, lpCmdLine)) { signal(1, SignalHandler); signal(3, SignalHandler); signal(5, SignalHandler); GNW95_isActive = true; RealMain(args.numArgs, args.args); DOSCmdLineDestroy(&args); return 1; } } } } CloseHandle(GNW95_mutex); } return 0; } // 0x4DE7F4 BOOL InitClass(HINSTANCE hInstance) { WNDCLASSA wc; wc.style = 3; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = "GNW95 Class"; return RegisterClassA(&wc); } // 0x4DE864 BOOL InitInstance() { OSVERSIONINFOA osvi; bool result; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); #pragma warning(suppress : 4996 28159) if (!GetVersionExA(&osvi)) { return true; } result = true; if (osvi.dwPlatformId == 0 || osvi.dwPlatformId == 2 && osvi.dwMajorVersion < 4) { result = false; } if (!result) { MessageBoxA(NULL, "This program requires Windows 95 or Windows NT version 4.0 or greater.", "Wrong Windows Version", MB_ICONSTOP); } return result; } // 0x4DE8D0 static BOOL LoadDirectX() { GNW95_hDDrawLib = LoadLibraryA("DDRAW.DLL"); if (GNW95_hDDrawLib == NULL) { goto err; } GNW95_DirectDrawCreate = (PFNDDRAWCREATE)GetProcAddress(GNW95_hDDrawLib, "DirectDrawCreate"); if (GNW95_DirectDrawCreate == NULL) { goto err; } GNW95_hDInputLib = LoadLibraryA("DINPUT.DLL"); if (GNW95_hDInputLib == NULL) { goto err; } GNW95_DirectInputCreate = (PFNDINPUTCREATE)GetProcAddress(GNW95_hDInputLib, "DirectInputCreateA"); if (GNW95_DirectInputCreate == NULL) { goto err; } GNW95_hDSoundLib = LoadLibraryA("DSOUND.DLL"); if (GNW95_hDSoundLib == NULL) { goto err; } GNW95_DirectSoundCreate = (PFNDSOUNDCREATE)GetProcAddress(GNW95_hDSoundLib, "DirectSoundCreate"); if (GNW95_DirectSoundCreate == NULL) { goto err; } atexit(UnloadDirectX); return TRUE; err: UnloadDirectX(); MessageBoxA(NULL, "This program requires Windows 95 with DirectX 3.0a or later or Windows NT version 4.0 with Service Pack 3 or greater.", "Could not load DirectX", MB_ICONSTOP); return FALSE; } // 0x4DE988 static void UnloadDirectX(void) { if (GNW95_hDSoundLib != NULL) { FreeLibrary(GNW95_hDSoundLib); GNW95_hDSoundLib = NULL; GNW95_DirectDrawCreate = NULL; } if (GNW95_hDDrawLib != NULL) { FreeLibrary(GNW95_hDDrawLib); GNW95_hDDrawLib = NULL; GNW95_DirectSoundCreate = NULL; } if (GNW95_hDInputLib != NULL) { FreeLibrary(GNW95_hDInputLib); GNW95_hDInputLib = NULL; GNW95_DirectInputCreate = NULL; } } // 0x4DE9F4 void SignalHandler(int sig) { win_exit(); } // 0x4DE9FC LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: exit(EXIT_SUCCESS); case WM_PAINT: if (1) { RECT updateRect; if (GetUpdateRect(hWnd, &updateRect, FALSE)) { Rect rect; rect.ulx = updateRect.left; rect.uly = updateRect.top; rect.lrx = updateRect.right - 1; rect.lry = updateRect.bottom - 1; win_refresh_all(&rect); } } break; case WM_ERASEBKGND: return 1; case WM_SETCURSOR: if ((HWND)wParam == GNW95_hwnd) { SetCursor(NULL); return 1; } break; case WM_SYSCOMMAND: switch (wParam & 0xFFF0) { case SC_SCREENSAVE: case SC_MONITORPOWER: return 0; } break; case WM_ACTIVATEAPP: GNW95_isActive = wParam; if (wParam) { GNW95_hook_input(1); win_refresh_all(&scr_size); } else { GNW95_hook_input(0); } return 0; } return DefWindowProc(hWnd, uMsg, wParam, lParam); } ================================================ FILE: src/plib/gnw/winmain.h ================================================ #ifndef FALLOUT_PLIB_GNW_WINMAIN_H_ #define FALLOUT_PLIB_GNW_WINMAIN_H_ #include #define WIN32_LEAN_AND_MEAN #include extern HWND GNW95_hwnd; extern HINSTANCE GNW95_hInstance; extern LPSTR GNW95_lpszCmdLine; extern int GNW95_nCmdShow; extern bool GNW95_isActive; extern HANDLE GNW95_mutex; extern HMODULE GNW95_hDDrawLib; extern HMODULE GNW95_hDInputLib; extern HMODULE GNW95_hDSoundLib; extern char GNW95_title[256]; BOOL InitClass(HINSTANCE hInstance); BOOL InitInstance(); void SignalHandler(int signalID); LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #endif /* FALLOUT_PLIB_GNW_WINMAIN_H_ */ ================================================ FILE: src/plib/xfile/dfile.c ================================================ #include "plib/xfile/dfile.h" #include #include #include #include #include #include static_assert(sizeof(DBase) == 20, "wrong size"); static_assert(sizeof(DBaseEntry) == 20, "wrong size"); static_assert(sizeof(DFile) == 44, "wrong size"); static_assert(sizeof(DFileFindData) == 524, "wrong size"); static int dinfo_bsearch_compare(const void* a1, const void* a2); static DFile* dfile_fopen_helper(DBase* dbase, const char* filename, const char* mode, DFile* a4); static int dfile_fgetc_helper(DFile* stream); static bool dfile_read_comp_bytes(DFile* stream, void* ptr, size_t size); static void dfile_zungetc(DFile* stream, int ch); // Reads .DAT file contents. // // 0x4E4F58 DBase* dbase_open(const char* filePath) { assert(filePath); // "filename", "dfile.c", 74 FILE* stream = fopen(filePath, "rb"); if (stream == NULL) { return NULL; } DBase* dbase = (DBase*)malloc(sizeof(*dbase)); if (dbase == NULL) { fclose(stream); return NULL; } memset(dbase, 0, sizeof(*dbase)); // Get file size, and reposition stream to read footer, which contains two // 32-bits ints. int fileSize = filelength(fileno(stream)); if (fseek(stream, fileSize - sizeof(int) * 2, SEEK_SET) != 0) { goto err; } // Read the size of entries table. int entriesDataSize; if (fread(&entriesDataSize, sizeof(entriesDataSize), 1, stream) != 1) { goto err; } // Read the size of entire dbase content. // // NOTE: It appears that this approach allows existence of arbitrary data in // the beginning of the .DAT file. int dbaseDataSize; if (fread(&dbaseDataSize, sizeof(dbaseDataSize), 1, stream) != 1) { goto err; } // Reposition stream to the beginning of the entries table. if (fseek(stream, fileSize - entriesDataSize - sizeof(int) * 2, SEEK_SET) != 0) { goto err; } if (fread(&(dbase->entriesLength), sizeof(dbase->entriesLength), 1, stream) != 1) { goto err; } dbase->entries = (DBaseEntry*)malloc(sizeof(*dbase->entries) * dbase->entriesLength); if (dbase->entries == NULL) { goto err; } memset(dbase->entries, 0, sizeof(*dbase->entries) * dbase->entriesLength); // Read entries one by one, stopping on any error. int entryIndex; for (entryIndex = 0; entryIndex < dbase->entriesLength; entryIndex++) { DBaseEntry* entry = &(dbase->entries[entryIndex]); int pathLength; if (fread(&pathLength, sizeof(pathLength), 1, stream) != 1) { break; } entry->path = (char*)malloc(pathLength + 1); if (entry->path == NULL) { break; } if (fread(entry->path, pathLength, 1, stream) != 1) { break; } entry->path[pathLength] = '\0'; if (fread(&(entry->compressed), sizeof(entry->compressed), 1, stream) != 1) { break; } if (fread(&(entry->uncompressedSize), sizeof(entry->uncompressedSize), 1, stream) != 1) { break; } if (fread(&(entry->dataSize), sizeof(entry->dataSize), 1, stream) != 1) { break; } if (fread(&(entry->dataOffset), sizeof(entry->dataOffset), 1, stream) != 1) { break; } } if (entryIndex < dbase->entriesLength) { // We haven't reached the end, which means there was an error while // reading entries. goto err; } dbase->path = strdup(filePath); dbase->dataOffset = fileSize - dbaseDataSize; fclose(stream); return dbase; err: dbase_close(dbase); fclose(stream); return NULL; } // Closes [dbase], all open file handles, frees all associated resources, // including the [dbase] itself. // // 0x4E5270 bool dbase_close(DBase* dbase) { assert(dbase); // "dbase", "dfile.c", 173 DFile* curr = dbase->dfileHead; while (curr != NULL) { DFile* next = curr->next; dfile_fclose(curr); curr = next; } if (dbase->entries != NULL) { for (int index = 0; index < dbase->entriesLength; index++) { DBaseEntry* entry = &(dbase->entries[index]); char* entryName = entry->path; if (entryName != NULL) { free(entryName); } } free(dbase->entries); } if (dbase->path != NULL) { free(dbase->path); } memset(dbase, 0, sizeof(*dbase)); free(dbase); return true; } // 0x4E5308 bool dbase_findfirst(DBase* dbase, DFileFindData* findFileData, const char* pattern) { for (int index = 0; index < dbase->entriesLength; index++) { DBaseEntry* entry = &(dbase->entries[index]); if (fpattern_match(pattern, entry->path)) { strcpy(findFileData->fileName, entry->path); strcpy(findFileData->pattern, pattern); findFileData->index = index; return true; } } return false; } // 0x4E53A0 bool dbase_findnext(DBase* dbase, DFileFindData* findFileData) { for (int index = findFileData->index + 1; index < dbase->entriesLength; index++) { DBaseEntry* entry = &(dbase->entries[index]); if (fpattern_match(findFileData->pattern, entry->path)) { strcpy(findFileData->fileName, entry->path); findFileData->index = index; return true; } } return false; } // 0x4E541C bool dbase_findclose(DBase* dbase, DFileFindData* findFileData) { return true; } // [filelength]. // // 0x4E5424 long dfile_filelength(DFile* stream) { return stream->entry->uncompressedSize; } // [fclose]. // // 0x4E542C int dfile_fclose(DFile* stream) { assert(stream); // "stream", "dfile.c", 253 int rc = 0; if (stream->entry->compressed == 1) { if (inflateEnd(stream->decompressionStream) != Z_OK) { rc = -1; } } if (stream->decompressionStream != NULL) { free(stream->decompressionStream); } if (stream->decompressionBuffer != NULL) { free(stream->decompressionBuffer); } if (stream->stream != NULL) { fclose(stream->stream); } // Loop thru open file handles and find previous to remove current handle // from linked list. // // NOTE: Compiled code is slightly different. DFile* curr = stream->dbase->dfileHead; DFile* prev = NULL; while (curr != NULL) { if (curr == stream) { break; } prev = curr; curr = curr->next; } if (curr != NULL) { if (prev == NULL) { stream->dbase->dfileHead = stream->next; } else { prev->next = stream->next; } } memset(stream, 0, sizeof(*stream)); free(stream); return rc; } // [fopen]. // // 0x4E5504 DFile* dfile_fopen(DBase* dbase, const char* filePath, const char* mode) { assert(dbase); // dfile.c, 295 assert(filePath); // dfile.c, 296 assert(mode); // dfile.c, 297 return dfile_fopen_helper(dbase, filePath, mode, 0); } // [vfprintf]. // // 0x4E56C0 int dfile_vfprintf(DFile* stream, const char* format, va_list args) { assert(stream); // "stream", "dfile.c", 368 assert(format); // "format", "dfile.c", 369 return -1; } // [fgetc]. // // This function reports \r\n sequence as one character \n, even though it // consumes two characters from the underlying stream. // // 0x4E5700 int dfile_fgetc(DFile* stream) { assert(stream); // "stream", "dfile.c", 384 if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) { return -1; } if ((stream->flags & DFILE_HAS_UNGETC) != 0) { stream->flags &= ~DFILE_HAS_UNGETC; return stream->ungotten; } int ch = dfile_fgetc_helper(stream); if (ch == -1) { stream->flags |= DFILE_EOF; } return ch; } // [fgets]. // // Both Windows (\r\n) and Unix (\n) line endings are recognized. Windows // line ending is reported as \n. // // 0x4E5764 char* dfile_fgets(char* string, int size, DFile* stream) { assert(string); // "s", "dfile.c", 407 assert(size); // "n", "dfile.c", 408 assert(stream); // "stream", "dfile.c", 409 if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) { return NULL; } char* pch = string; if ((stream->flags & DFILE_HAS_UNGETC) != 0) { *pch++ = stream->ungotten & 0xFF; size--; stream->flags &= ~DFILE_HAS_UNGETC; } // Read up to size - 1 characters one by one saving space for the null // terminator. for (int index = 0; index < size - 1; index++) { int ch = dfile_fgetc_helper(stream); if (ch == -1) { break; } *pch++ = ch & 0xFF; if (ch == '\n') { break; } } if (pch == string) { // No character was set into the buffer. return NULL; } *pch = '\0'; return string; } // [fputc]. // // 0x4E5830 int dfile_fputc(int ch, DFile* stream) { assert(stream); // "stream", "dfile.c", 437 return -1; } // [fputs]. // // 0x4E5854 int dfile_fputs(const char* string, DFile* stream) { assert(string); // "s", "dfile.c", 448 assert(stream); // "stream", "dfile.c", 449 return -1; } // [fread]. // // 0x4E58FC size_t dfile_fread(void* ptr, size_t size, size_t count, DFile* stream) { assert(ptr); // "ptr", "dfile.c", 499 assert(stream); // "stream", dfile.c, 500 if ((stream->flags & DFILE_EOF) != 0 || (stream->flags & DFILE_ERROR) != 0) { return 0; } size_t remainingSize = stream->entry->uncompressedSize - stream->position; if ((stream->flags & DFILE_HAS_UNGETC) != 0) { remainingSize++; } size_t bytesToRead = size * count; if (remainingSize < bytesToRead) { bytesToRead = remainingSize; stream->flags |= DFILE_EOF; } size_t extraBytesRead = 0; if ((stream->flags & DFILE_HAS_UNGETC) != 0) { unsigned char* byteBuffer = (unsigned char*)ptr; *byteBuffer++ = stream->ungotten & 0xFF; ptr = byteBuffer; bytesToRead--; stream->flags &= ~DFILE_HAS_UNGETC; extraBytesRead = 1; } size_t bytesRead; if (stream->entry->compressed == 1) { if (!dfile_read_comp_bytes(stream, ptr, bytesToRead)) { stream->flags |= DFILE_ERROR; return false; } bytesRead = bytesToRead; } else { bytesRead = fread(ptr, 1, bytesToRead, stream->stream) + extraBytesRead; stream->position += bytesRead; } return bytesRead / size; } // [fwrite]. // // 0x4E59F8 size_t dfile_fwrite(const void* ptr, size_t size, size_t count, DFile* stream) { assert(ptr); // "ptr", "dfile.c", 538 assert(stream); // "stream", "dfile.c", 539 return count - 1; } // [fseek]. // // 0x4E5A74 int dfile_fseek(DFile* stream, long offset, int origin) { assert(stream); // "stream", "dfile.c", 569 if ((stream->flags & DFILE_ERROR) != 0) { return 1; } if ((stream->flags & DFILE_TEXT) != 0) { if (offset != 0 && origin != SEEK_SET) { // NOTE: For unknown reason this function does not allow arbitrary // seeks in text streams, whether compressed or not. It only // supports rewinding. Probably because of reading functions which // handle \r\n sequence as \n. return 1; } } long offsetFromBeginning; switch (origin) { case SEEK_SET: offsetFromBeginning = offset; break; case SEEK_CUR: offsetFromBeginning = stream->position + offset; break; case SEEK_END: offsetFromBeginning = stream->entry->uncompressedSize + offset; break; default: return 1; } if (offsetFromBeginning >= stream->entry->uncompressedSize) { return 1; } long pos = stream->position; if (offsetFromBeginning == pos) { stream->flags &= ~(DFILE_HAS_UNGETC | DFILE_EOF); return 0; } if (offsetFromBeginning != 0) { if (stream->entry->compressed == 1) { if (offsetFromBeginning < pos) { // We cannot go backwards in compressed stream, so the only way // is to start from the beginning. dfile_rewind(stream); } // Consume characters one by one until we reach specified offset. while (offsetFromBeginning > stream->position) { if (dfile_fgetc_helper(stream) == -1) { return 1; } } } else { if (fseek(stream->stream, offsetFromBeginning - pos, SEEK_CUR) != 0) { stream->flags |= DFILE_ERROR; return 1; } // FIXME: I'm not sure what this assignment means. This field is // only meaningful when reading compressed streams. stream->compressedBytesRead = offsetFromBeginning; } stream->flags &= ~(DFILE_HAS_UNGETC | DFILE_EOF); return 0; } if (fseek(stream->stream, stream->dbase->dataOffset + stream->entry->dataOffset, SEEK_SET) != 0) { stream->flags |= DFILE_ERROR; return 1; } if (inflateEnd(stream->decompressionStream) != Z_OK) { stream->flags |= DFILE_ERROR; return 1; } stream->decompressionStream->zalloc = Z_NULL; stream->decompressionStream->zfree = Z_NULL; stream->decompressionStream->opaque = Z_NULL; stream->decompressionStream->next_in = stream->decompressionBuffer; stream->decompressionStream->avail_in = 0; if (inflateInit(stream->decompressionStream) != Z_OK) { stream->flags |= DFILE_ERROR; return 1; } stream->position = 0; stream->compressedBytesRead = 0; stream->flags &= ~(DFILE_HAS_UNGETC | DFILE_EOF); return 0; } // [ftell]. // // 0x4E5C88 long dfile_ftell(DFile* stream) { assert(stream); // "stream", "dfile.c", 654 return stream->position; } // [rewind]. // // 0x4E5CB0 void dfile_rewind(DFile* stream) { assert(stream); // "stream", "dfile.c", 664 dfile_fseek(stream, 0, SEEK_SET); stream->flags &= ~DFILE_ERROR; } // [feof]. // // 0x4E5D10 int dfile_feof(DFile* stream) { assert(stream); // "stream", "dfile.c", 685 return stream->flags & DFILE_EOF; } // The [bsearch] comparison callback, which is used to find [DBaseEntry] for // specified [filePath]. // // 0x4E5D70 static int dinfo_bsearch_compare(const void* a1, const void* a2) { const char* filePath = (const char*)a1; DBaseEntry* entry = (DBaseEntry*)a2; return stricmp(filePath, entry->path); } // 0x4E5D9C static DFile* dfile_fopen_helper(DBase* dbase, const char* filePath, const char* mode, DFile* dfile) { DBaseEntry* entry = (DBaseEntry*)bsearch(filePath, dbase->entries, dbase->entriesLength, sizeof(*dbase->entries), dinfo_bsearch_compare); if (entry == NULL) { goto err; } if (mode[0] != 'r') { goto err; } if (dfile == NULL) { dfile = (DFile*)malloc(sizeof(*dfile)); if (dfile == NULL) { return NULL; } memset(dfile, 0, sizeof(*dfile)); dfile->dbase = dbase; dfile->next = dbase->dfileHead; dbase->dfileHead = dfile; } else { if (dbase != dfile->dbase) { goto err; } if (dfile->stream != NULL) { fclose(dfile->stream); dfile->stream = NULL; } dfile->compressedBytesRead = 0; dfile->position = 0; dfile->flags = 0; } dfile->entry = entry; // Open stream to .DAT file. dfile->stream = fopen(dbase->path, "rb"); if (dfile->stream == NULL) { goto err; } // Relocate stream to the beginning of data for specified entry. if (fseek(dfile->stream, dbase->dataOffset + entry->dataOffset, SEEK_SET) != 0) { goto err; } if (entry->compressed == 1) { // Entry is compressed, setup decompression stream and decompression // buffer. This step is not needed when previous instance of dfile is // passed via parameter, which might already have stream and // buffer allocated. if (dfile->decompressionStream == NULL) { dfile->decompressionStream = (z_streamp)malloc(sizeof(*dfile->decompressionStream)); if (dfile->decompressionStream == NULL) { goto err; } dfile->decompressionBuffer = (unsigned char*)malloc(DFILE_DECOMPRESSION_BUFFER_SIZE); if (dfile->decompressionBuffer == NULL) { goto err; } } dfile->decompressionStream->zalloc = Z_NULL; dfile->decompressionStream->zfree = Z_NULL; dfile->decompressionStream->opaque = Z_NULL; dfile->decompressionStream->next_in = dfile->decompressionBuffer; dfile->decompressionStream->avail_in = 0; if (inflateInit(dfile->decompressionStream) != Z_OK) { goto err; } } else { // Entry is not compressed, there is no need to keep decompression // stream and decompression buffer (in case [dfile] was passed via // parameter). if (dfile->decompressionStream != NULL) { free(dfile->decompressionStream); dfile->decompressionStream = NULL; } if (dfile->decompressionBuffer != NULL) { free(dfile->decompressionBuffer); dfile->decompressionBuffer = NULL; } } if (mode[1] == 't') { dfile->flags |= DFILE_TEXT; } return dfile; err: if (dfile != NULL) { dfile_fclose(dfile); } return NULL; } // 0x4E5F9C static int dfile_fgetc_helper(DFile* stream) { if (stream->entry->compressed == 1) { char ch; if (!dfile_read_comp_bytes(stream, &ch, sizeof(ch))) { return -1; } if ((stream->flags & DFILE_TEXT) != 0) { // NOTE: I'm not sure if they are comparing as chars or ints. Since // character literals are ints, let's cast read characters to int as // well. if (ch == '\r') { char nextCh; if (dfile_read_comp_bytes(stream, &nextCh, sizeof(nextCh))) { if (nextCh == '\n') { ch = nextCh; } else { // NOTE: Uninline. dfile_zungetc(stream, nextCh & 0xFF); } } } } return ch & 0xFF; } if (stream->position >= stream->entry->uncompressedSize) { return -1; } int ch = fgetc(stream->stream); if (ch != -1) { if ((stream->flags & DFILE_TEXT) != 0) { // This is a text stream, attempt to detect \r\n sequence. if (ch == '\r') { if (stream->position + 1 < stream->entry->uncompressedSize) { int nextCh = fgetc(stream->stream); if (nextCh == '\n') { ch = nextCh; stream->position++; } else { ungetc(nextCh, stream->stream); } } } } stream->position++; } return ch; } // 0x4E6078 static bool dfile_read_comp_bytes(DFile* stream, void* ptr, size_t size) { if ((stream->flags & DFILE_HAS_COMPRESSED_UNGETC) != 0) { unsigned char* byteBuffer = (unsigned char*)ptr; *byteBuffer++ = stream->compressedUngotten & 0xFF; ptr = byteBuffer; size--; stream->flags &= ~DFILE_HAS_COMPRESSED_UNGETC; stream->position++; if (size == 0) { return true; } } stream->decompressionStream->next_out = (Bytef*)ptr; stream->decompressionStream->avail_out = size; do { if (stream->decompressionStream->avail_out == 0) { // Everything was decompressed. break; } if (stream->decompressionStream->avail_in == 0) { // No more unprocessed data, request next chunk. size_t bytesToRead = stream->entry->dataSize - stream->compressedBytesRead; if (bytesToRead > DFILE_DECOMPRESSION_BUFFER_SIZE) { bytesToRead = DFILE_DECOMPRESSION_BUFFER_SIZE; } if (fread(stream->decompressionBuffer, bytesToRead, 1, stream->stream) != 1) { break; } stream->decompressionStream->avail_in = bytesToRead; stream->decompressionStream->next_in = stream->decompressionBuffer; stream->compressedBytesRead += bytesToRead; } } while (inflate(stream->decompressionStream, Z_NO_FLUSH) == Z_OK); if (stream->decompressionStream->avail_out != 0) { // There are some data still waiting, which means there was in error // during decompression loop above. return false; } stream->position += size; return true; } // NOTE: Inlined. // // 0x4E613C static void dfile_zungetc(DFile* stream, int ch) { stream->compressedUngotten = ch; stream->flags |= DFILE_HAS_COMPRESSED_UNGETC; stream->position--; } ================================================ FILE: src/plib/xfile/dfile.h ================================================ #ifndef FALLOUT_PLIB_XFILE_DFILE_H_ #define FALLOUT_PLIB_XFILE_DFILE_H_ #include #include #define WIN32_LEAN_AND_MEAN #include #include // The size of decompression buffer for reading compressed [DFile]s. #define DFILE_DECOMPRESSION_BUFFER_SIZE (0x400) // Specifies that [DFile] has unget character. // // NOTE: There is an unused function at 0x4E5894 which ungets one character and // stores it in [ungotten]. Since that function is not used, this flag will // never be set. #define DFILE_HAS_UNGETC (0x01) // Specifies that [DFile] has reached end of stream. #define DFILE_EOF (0x02) // Specifies that [DFile] is in error state. // // [dfile_rewind] can be used to clear this flag. #define DFILE_ERROR (0x04) // Specifies that [DFile] was opened in text mode. #define DFILE_TEXT (0x08) // Specifies that [DFile] has unget compressed character. #define DFILE_HAS_COMPRESSED_UNGETC (0x10) typedef struct DBase DBase; typedef struct DBaseEntry DBaseEntry; typedef struct DFile DFile; // A representation of .DAT file. typedef struct DBase { // The path of .DAT file that this structure represents. char* path; // The offset to the beginning of data section of .DAT file. int dataOffset; // The number of entries. int entriesLength; // The array of entries. DBaseEntry* entries; // The head of linked list of open file handles. DFile* dfileHead; } DBase; typedef struct DBaseEntry { char* path; unsigned char compressed; int uncompressedSize; int dataSize; int dataOffset; } DBaseEntry; // A handle to open entry in .DAT file. typedef struct DFile { DBase* dbase; DBaseEntry* entry; int flags; // The stream of .DAT file opened for reading in binary mode. // // This stream is not shared across open handles. Instead every [DFile] // opens it's own stream via [fopen], which is then closed via [fclose] in // [dfile_fclose]. FILE* stream; // The inflate stream used to decompress data. // // This value is NULL if entry is not compressed. z_streamp decompressionStream; // The decompression buffer of size [DFILE_DECOMPRESSION_BUFFER_SIZE]. // // This value is NULL if entry is not compressed. unsigned char* decompressionBuffer; // The last ungot character. // // See [DFILE_HAS_UNGETC] notes. int ungotten; // The last ungot compressed character. // // This value is used when reading compressed text streams to detect // Windows end of line sequence \r\n. int compressedUngotten; // The number of bytes read so far from compressed stream. // // This value is only used when reading compressed streams. The range is // 0..entry->dataSize. int compressedBytesRead; // The position in read stream. // // This value is tracked in terms of uncompressed data (even in compressed // streams). The range is 0..entry->uncompressedSize. long position; // Next [DFile] in linked list. // // [DFile]s are stored in [DBase] in reverse order, so it's actually a // previous opened file, not next. DFile* next; } DFile; typedef struct DFileFindData { // The name of file that was found during previous search. char fileName[MAX_PATH]; // The pattern to search. // // This value is set automatically when [dbase_findfirst] succeeds so // that subsequent calls to [dbase_findnext] know what to look for. char pattern[MAX_PATH]; // The index of entry that was found during previous search. // // This value is set automatically when [dbase_findfirst] and // [dbase_findnext] succeed so that subsequent calls to [dbase_findnext] // knows where to start search from. int index; } DFileFindData; DBase* dbase_open(const char* filename); bool dbase_close(DBase* dbase); bool dbase_findfirst(DBase* dbase, DFileFindData* findFileData, const char* pattern); bool dbase_findnext(DBase* dbase, DFileFindData* findFileData); bool dbase_findclose(DBase* dbase, DFileFindData* findFileData); long dfile_filelength(DFile* stream); int dfile_fclose(DFile* stream); DFile* dfile_fopen(DBase* dbase, const char* filename, const char* mode); int dfile_vfprintf(DFile* stream, const char* format, va_list args); int dfile_fgetc(DFile* stream); char* dfile_fgets(char* str, int size, DFile* stream); int dfile_fputc(int ch, DFile* stream); int dfile_fputs(const char* s, DFile* stream); size_t dfile_fread(void* ptr, size_t size, size_t count, DFile* stream); size_t dfile_fwrite(const void* ptr, size_t size, size_t count, DFile* stream); int dfile_fseek(DFile* stream, long offset, int origin); long dfile_ftell(DFile* stream); void dfile_rewind(DFile* stream); int dfile_feof(DFile* stream); #endif /* FALLOUT_PLIB_XFILE_DFILE_H_ */ ================================================ FILE: src/plib/xfile/xfile.c ================================================ #include "plib/xfile/xfile.h" #include #include #include #include #include #include #include "plib/xfile/xsys_find.h" static void xclearpath(); static void xpathexit(void); static bool xlistenumfunc(XListEnumerationContext* context); // 0x6B24D0 static XBase* paths; // 0x4DED6C int xfclose(XFile* stream) { assert(stream); // "stream", "xfile.c", 112 int rc; switch (stream->type) { case XFILE_TYPE_DFILE: rc = dfile_fclose(stream->dfile); break; case XFILE_TYPE_GZFILE: rc = gzclose(stream->gzfile); break; default: rc = fclose(stream->file); break; } memset(stream, 0, sizeof(*stream)); free(stream); return rc; } // 0x4DEE2C XFile* xfopen(const char* filePath, const char* mode) { assert(filePath); // "filename", "xfile.c", 162 assert(mode); // "mode", "xfile.c", 163 XFile* stream = (XFile*)malloc(sizeof(*stream)); if (stream == NULL) { return NULL; } memset(stream, 0, sizeof(*stream)); // NOTE: Compiled code uses different lengths. char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; _splitpath(filePath, drive, dir, NULL, NULL); char path[FILENAME_MAX]; if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') { // [filePath] is an absolute path. Attempt to open as plain stream. stream->file = fopen(filePath, mode); if (stream->file == NULL) { free(stream); return NULL; } stream->type = XFILE_TYPE_FILE; sprintf(path, filePath); } else { // [filePath] is a relative path. Loop thru open xbases and attempt to // open [filePath] from appropriate xbase. XBase* curr = paths; while (curr != NULL) { if (curr->isDbase) { // Attempt to open dfile stream from dbase. stream->dfile = dfile_fopen(curr->dbase, filePath, mode); if (stream->dfile != NULL) { stream->type = XFILE_TYPE_DFILE; sprintf(path, filePath); break; } } else { // Build path relative to directory-based xbase. sprintf(path, "%s\\%s", curr->path, filePath); // Attempt to open plain stream. stream->file = fopen(path, mode); if (stream->file != NULL) { stream->type = XFILE_TYPE_FILE; break; } } curr = curr->next; } if (stream->file == NULL) { // File was not opened during the loop above. Attempt to open file // relative to the current working directory. stream->file = fopen(filePath, mode); if (stream->file == NULL) { free(stream); return NULL; } stream->type = XFILE_TYPE_FILE; sprintf(path, filePath); } } if (stream->type == XFILE_TYPE_FILE) { // Opened file is a plain stream, which might be gzipped. In this case // first two bytes will contain magic numbers. int ch1 = fgetc(stream->file); int ch2 = fgetc(stream->file); if (ch1 == 0x1F && ch2 == 0x8B) { // File is gzipped. Close plain stream and reopen this file as // gzipped stream. fclose(stream->file); stream->type = XFILE_TYPE_GZFILE; stream->gzfile = gzopen(path, mode); } else { // File is not gzipped. rewind(stream->file); } } return stream; } // 0x4DF11C int xfprintf(XFile* stream, const char* format, ...) { assert(format); // "format", "xfile.c", 305 va_list args; va_start(args, format); int rc = xvfprintf(stream, format, args); va_end(args); return rc; } // [vfprintf]. // // 0x4DF1AC int xvfprintf(XFile* stream, const char* format, va_list args) { assert(stream); // "stream", "xfile.c", 332 assert(format); // "format", "xfile.c", 333 int rc; switch (stream->type) { case XFILE_TYPE_DFILE: rc = dfile_vfprintf(stream->dfile, format, args); break; case XFILE_TYPE_GZFILE: rc = gzvprintf(stream->gzfile, format, args); break; default: rc = vfprintf(stream->file, format, args); break; } return rc; } // 0x4DF22C int xfgetc(XFile* stream) { assert(stream); // "stream", "xfile.c", 354 int ch; switch (stream->type) { case XFILE_TYPE_DFILE: ch = dfile_fgetc(stream->dfile); break; case XFILE_TYPE_GZFILE: ch = gzgetc(stream->gzfile); break; default: ch = fgetc(stream->file); break; } return ch; } // 0x4DF280 char* xfgets(char* string, int size, XFile* stream) { assert(string); // "s", "xfile.c", 375 assert(size); // "n", "xfile.c", 376 assert(stream); // "stream", "xfile.c", 377 char* result; switch (stream->type) { case XFILE_TYPE_DFILE: result = dfile_fgets(string, size, stream->dfile); break; case XFILE_TYPE_GZFILE: result = gzgets(stream->gzfile, string, size); break; default: result = fgets(string, size, stream->file); break; } return result; } // 0x4DF320 int xfputc(int ch, XFile* stream) { assert(stream); // "stream", "xfile.c", 399 int rc; switch (stream->type) { case XFILE_TYPE_DFILE: rc = dfile_fputc(ch, stream->dfile); break; case XFILE_TYPE_GZFILE: rc = gzputc(stream->gzfile, ch); break; default: rc = fputc(ch, stream->file); break; } return rc; } // 0x4DF380 int xfputs(const char* string, XFile* stream) { assert(string); // "s", "xfile.c", 421 assert(stream); // "stream", "xfile.c", 422 int rc; switch (stream->type) { case XFILE_TYPE_DFILE: rc = dfile_fputs(string, stream->dfile); break; case XFILE_TYPE_GZFILE: rc = gzputs(stream->gzfile, string); break; default: rc = fputs(string, stream->file); break; } return rc; } // 0x4DF44C size_t xfread(void* ptr, size_t size, size_t count, XFile* stream) { assert(ptr); // "ptr", "xfile.c", 421 assert(stream); // "stream", "xfile.c", 422 size_t elementsRead; switch (stream->type) { case XFILE_TYPE_DFILE: elementsRead = dfile_fread(ptr, size, count, stream->dfile); break; case XFILE_TYPE_GZFILE: // FIXME: There is a bug in the return value. Both [dfile_fread] and // [fread] returns number of elements read, but [gzwrite] have no such // concept, it works with bytes, and returns number of bytes read. // Depending on the [size] and [count] parameters this function can // return wrong result. elementsRead = gzread(stream->gzfile, ptr, size * count); break; default: elementsRead = fread(ptr, size, count, stream->file); break; } return elementsRead; } // 0x4DF4E8 size_t xfwrite(const void* ptr, size_t size, size_t count, XFile* stream) { assert(ptr); // "ptr", "xfile.c", 504 assert(stream); // "stream", "xfile.c", 505 size_t elementsWritten; switch (stream->type) { case XFILE_TYPE_DFILE: elementsWritten = dfile_fwrite(ptr, size, count, stream->dfile); break; case XFILE_TYPE_GZFILE: // FIXME: There is a bug in the return value. [fwrite] returns number // of elements written (while [dfile_fwrite] does not support writing at // all), but [gzwrite] have no such concept, it works with bytes, and // returns number of bytes written. Depending on the [size] and [count] // parameters this function can return wrong result. elementsWritten = gzwrite(stream->gzfile, ptr, size * count); break; default: elementsWritten = fwrite(ptr, size, count, stream->file); break; } return elementsWritten; } // 0x4DF5D8 int xfseek(XFile* stream, long offset, int origin) { assert(stream); // "stream", "xfile.c", 547 int result; switch (stream->type) { case XFILE_TYPE_DFILE: result = dfile_fseek(stream->dfile, offset, origin); break; case XFILE_TYPE_GZFILE: result = gzseek(stream->gzfile, offset, origin); break; default: result = fseek(stream->file, offset, origin); break; } return result; } // 0x4DF690 long xftell(XFile* stream) { assert(stream); // "stream", "xfile.c", 588 long pos; switch (stream->type) { case XFILE_TYPE_DFILE: pos = dfile_ftell(stream->dfile); break; case XFILE_TYPE_GZFILE: pos = gztell(stream->gzfile); break; default: pos = ftell(stream->file); break; } return pos; } // 0x4DF6E4 void xrewind(XFile* stream) { assert(stream); // "stream", "xfile.c", 608 switch (stream->type) { case XFILE_TYPE_DFILE: dfile_rewind(stream->dfile); break; case XFILE_TYPE_GZFILE: gzrewind(stream->gzfile); break; default: rewind(stream->file); break; } } // 0x4DF780 int xfeof(XFile* stream) { assert(stream); // "stream", "xfile.c", 648 int rc; switch (stream->type) { case XFILE_TYPE_DFILE: rc = dfile_feof(stream->dfile); break; case XFILE_TYPE_GZFILE: rc = gzeof(stream->gzfile); break; default: rc = feof(stream->file); break; } return rc; } // 0x4DF828 long xfilelength(XFile* stream) { assert(stream); // "stream", "xfile.c", 690 long fileSize; switch (stream->type) { case XFILE_TYPE_DFILE: fileSize = dfile_filelength(stream->dfile); break; case XFILE_TYPE_GZFILE: fileSize = 0; break; default: fileSize = filelength(fileno(stream->file)); break; } return fileSize; } // Closes all open xbases and opens a set of xbases specified by [paths]. // // [paths] is a set of paths separated by semicolon. Can be NULL, in this case // all open xbases are simply closed. // // 0x4DF878 bool xsetpath(char* paths) { // NOTE: Uninline. xclearpath(); if (paths != NULL) { char* tok = strtok(paths, ";"); while (tok != NULL) { if (!xaddpath(tok)) { return false; } tok = strtok(NULL, ";"); } } return true; } // 0x4DF938 bool xaddpath(const char* path) { // 0x6B24D4 static bool init; assert(path); // "path", "xfile.c", 747 // Register atexit handler so that underlying dbase (if any) can be // gracefully closed. if (!init) { atexit(xpathexit); init = true; } XBase* curr = paths; XBase* prev = NULL; while (curr != NULL) { if (stricmp(path, curr->path) == 0) { break; } prev = curr; curr = curr->next; } if (curr != NULL) { if (prev != NULL) { // Move found xbase to the top. prev->next = curr->next; curr->next = paths; paths = curr; } return true; } XBase* xbase = (XBase*)malloc(sizeof(*xbase)); if (xbase == NULL) { return false; } memset(xbase, 0, sizeof(*xbase)); xbase->path = strdup(path); if (xbase->path == NULL) { free(xbase); return false; } DBase* dbase = dbase_open(path); if (dbase != NULL) { xbase->isDbase = true; xbase->dbase = dbase; xbase->next = paths; paths = xbase; return true; } char workingDirectory[FILENAME_MAX]; if (getcwd(workingDirectory, FILENAME_MAX) == NULL) { // FIXME: Leaking xbase and path. return false; } if (chdir(path) == 0) { chdir(workingDirectory); xbase->next = paths; paths = xbase; return true; } if (xmkdir(path) != 0) { // FIXME: Leaking xbase and path. return false; } chdir(workingDirectory); xbase->next = paths; paths = xbase; return true; } // 0x4DFB3C bool xenumpath(const char* pattern, XListEnumerationHandler* handler, XList* xlist) { assert(pattern); // "filespec", "xfile.c", 845 assert(handler); // "enumfunc", "xfile.c", 846 DirectoryFileFindData directoryFileFindData; XListEnumerationContext context; context.xlist = xlist; char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fileName[_MAX_FNAME]; char extension[_MAX_EXT]; _splitpath(pattern, drive, dir, fileName, extension); if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') { if (xsys_findfirst(pattern, &directoryFileFindData)) { do { bool isDirectory = fileFindIsDirectory(&directoryFileFindData); char* entryName = fileFindGetName(&directoryFileFindData); if (isDirectory) { if (strcmp(entryName, "..") == 0 || strcmp(entryName, ".") == 0) { continue; } context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY; } else { context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE; } _makepath(context.name, drive, dir, entryName, NULL); if (!handler(&context)) { break; } } while (xsys_findnext(&directoryFileFindData)); } return xsys_findclose(&directoryFileFindData); } XBase* xbase = paths; while (xbase != NULL) { if (xbase->isDbase) { DFileFindData dbaseFindData; if (dbase_findfirst(xbase->dbase, &dbaseFindData, pattern)) { context.type = XFILE_ENUMERATION_ENTRY_TYPE_DFILE; do { strcpy(context.name, dbaseFindData.fileName); if (!handler(&context)) { return dbase_findclose(xbase->dbase, &dbaseFindData); } } while (dbase_findnext(xbase->dbase, &dbaseFindData)); dbase_findclose(xbase->dbase, &dbaseFindData); } } else { char path[FILENAME_MAX]; sprintf(path, "%s\\%s", xbase->path, pattern); if (xsys_findfirst(path, &directoryFileFindData)) { do { bool isDirectory = fileFindIsDirectory(&directoryFileFindData); char* entryName = fileFindGetName(&directoryFileFindData); if (isDirectory) { if (strcmp(entryName, "..") == 0 || strcmp(entryName, ".") == 0) { continue; } context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY; } else { context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE; } _makepath(context.name, drive, dir, entryName, NULL); if (!handler(&context)) { break; } } while (xsys_findnext(&directoryFileFindData)); } xsys_findclose(&directoryFileFindData); } xbase = xbase->next; } _splitpath(pattern, drive, dir, fileName, extension); if (xsys_findfirst(pattern, &directoryFileFindData)) { do { bool isDirectory = fileFindIsDirectory(&directoryFileFindData); char* entryName = fileFindGetName(&directoryFileFindData); if (isDirectory) { if (strcmp(entryName, "..") == 0 || strcmp(entryName, ".") == 0) { continue; } context.type = XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY; } else { context.type = XFILE_ENUMERATION_ENTRY_TYPE_FILE; } _makepath(context.name, drive, dir, entryName, NULL); if (!handler(&context)) { break; } } while (xsys_findnext(&directoryFileFindData)); } return xsys_findclose(&directoryFileFindData); } // 0x4DFF28 bool xbuild_filelist(const char* pattern, XList* xlist) { xenumpath(pattern, xlistenumfunc, xlist); return xlist->fileNamesLength != -1; } // 0x4DFF48 void xfree_filelist(XList* xlist) { assert(xlist); // "list", "xfile.c", 949 for (int index = 0; index < xlist->fileNamesLength; index++) { if (xlist->fileNames[index] != NULL) { free(xlist->fileNames[index]); } } free(xlist->fileNames); memset(xlist, 0, sizeof(*xlist)); } // Recursively creates specified file path. // // 0x4DFFAC int xmkdir(const char* filePath) { char workingDirectory[FILENAME_MAX]; if (getcwd(workingDirectory, FILENAME_MAX) == NULL) { return -1; } char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; _splitpath(filePath, drive, dir, NULL, NULL); char path[FILENAME_MAX]; if (drive[0] != '\0' || dir[0] == '\\' || dir[0] == '/' || dir[0] == '.') { // [filePath] is an absolute path. strcpy(path, filePath); } else { // Find first directory-based xbase. XBase* curr = paths; while (curr != NULL) { if (!curr->isDbase) { sprintf(path, "%s\\%s", curr->path, filePath); break; } curr = curr->next; } if (curr == NULL) { // Either there are no directory-based xbase, or there are no open // xbases at all - resolve path against current working directory. sprintf(path, "%s\\%s", workingDirectory, filePath); } } char* pch = path; if (*pch == '\\' || *pch == '/') { pch++; } while (*pch != '\0') { if (*pch == '\\' || *pch == '/') { char temp = *pch; *pch = '\0'; if (chdir(path) != 0) { if (mkdir(path) != 0) { chdir(workingDirectory); return -1; } } else { chdir(workingDirectory); } *pch = temp; } pch++; } // Last path component. mkdir(path); chdir(workingDirectory); return 0; } // Closes all xbases. // // NOTE: Inlined. // // 0x4E01F8 static void xclearpath() { XBase* curr = paths; paths = NULL; while (curr != NULL) { XBase* next = curr->next; if (curr->isDbase) { dbase_close(curr->dbase); } free(curr->path); free(curr); curr = next; } } // xbase atexit static void xpathexit(void) { // NOTE: Uninline. xclearpath(); } // 0x4E0278 static bool xlistenumfunc(XListEnumerationContext* context) { if (context->type == XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY) { return true; } XList* xlist = context->xlist; char** fileNames = (char**)realloc(xlist->fileNames, sizeof(*fileNames) * (xlist->fileNamesLength + 1)); if (fileNames == NULL) { xfree_filelist(xlist); xlist->fileNamesLength = -1; return false; } xlist->fileNames = fileNames; fileNames[xlist->fileNamesLength] = strdup(context->name); if (fileNames[xlist->fileNamesLength] == NULL) { xfree_filelist(xlist); xlist->fileNamesLength = -1; return false; } xlist->fileNamesLength++; return true; } ================================================ FILE: src/plib/xfile/xfile.h ================================================ #ifndef FALLOUT_PLIB_XFILE_XFILE_H_ #define FALLOUT_PLIB_XFILE_XFILE_H_ #include #include #include #include "plib/xfile/dfile.h" typedef enum XFileType { XFILE_TYPE_FILE, XFILE_TYPE_DFILE, XFILE_TYPE_GZFILE, } XFileType; // A universal database of files. typedef struct XBase { // The path to directory or .DAT file that this xbase represents. char* path; // The [DBase] instance that this xbase represents. DBase* dbase; // A flag used to denote that this xbase represents .DAT file (true), or // a directory (false). // // NOTE: Original type is 1 byte, likely unsigned char. bool isDbase; // Next [XBase] in linked list. struct XBase* next; } XBase; typedef struct XFile { XFileType type; union { FILE* file; DFile* dfile; gzFile gzfile; }; } XFile; typedef struct XList { int fileNamesLength; char** fileNames; } XList; typedef enum XFileEnumerationEntryType { XFILE_ENUMERATION_ENTRY_TYPE_FILE, XFILE_ENUMERATION_ENTRY_TYPE_DIRECTORY, XFILE_ENUMERATION_ENTRY_TYPE_DFILE, } XFileEnumerationEntryType; typedef struct XListEnumerationContext { char name[FILENAME_MAX]; unsigned char type; XList* xlist; } XListEnumerationContext; typedef bool(XListEnumerationHandler)(XListEnumerationContext* context); int xfclose(XFile* stream); XFile* xfopen(const char* filename, const char* mode); int xfprintf(XFile* xfile, const char* format, ...); int xvfprintf(XFile* stream, const char* format, va_list args); int xfgetc(XFile* stream); char* xfgets(char* string, int size, XFile* stream); int xfputc(int ch, XFile* stream); int xfputs(const char* s, XFile* stream); size_t xfread(void* ptr, size_t size, size_t count, XFile* stream); size_t xfwrite(const void* buf, size_t size, size_t count, XFile* stream); int xfseek(XFile* stream, long offset, int origin); long xftell(XFile* stream); void xrewind(XFile* stream); int xfeof(XFile* stream); long xfilelength(XFile* stream); bool xsetpath(char* paths); bool xaddpath(const char* path); bool xenumpath(const char* pattern, XListEnumerationHandler* handler, XList* xlist); bool xbuild_filelist(const char* pattern, XList* xlist); void xfree_filelist(XList* xlist); int xmkdir(const char* path); #endif /* FALLOUT_PLIB_XFILE_XFILE_H_ */ ================================================ FILE: src/plib/xfile/xsys_find.c ================================================ #include "plib/xfile/xsys_find.h" // 0x4E6380 bool xsys_findfirst(const char* path, DirectoryFileFindData* findData) { #if defined(_MSC_VER) findData->hFind = FindFirstFileA(path, &(findData->ffd)); if (findData->hFind == INVALID_HANDLE_VALUE) { return false; } #elif defined(__WATCOMC__) findData->dir = opendir(path); if (findData->dir == NULL) { return false; } findData->entry = readdir(findData->dir); if (findData->entry == NULL) { closedir(findData->dir); return false; } #else #error Not implemented #endif return true; } // 0x4E63A8 bool xsys_findnext(DirectoryFileFindData* findData) { #if defined(_MSC_VER) if (!FindNextFileA(findData->hFind, &(findData->ffd))) { return false; } #elif defined(__WATCOMC__) findData->entry = readdir(findData->dir); if (findData->entry == NULL) { closedir(findData->dir); return false; } #else #error Not implemented #endif return true; } // 0x4E63CC bool xsys_findclose(DirectoryFileFindData* findData) { #if defined(_MSC_VER) FindClose(findData->hFind); #elif defined(__WATCOMC__) if (closedir(findData->dir) != 0) { return false; } #else #error Not implemented #endif return true; } ================================================ FILE: src/plib/xfile/xsys_find.h ================================================ #ifndef FALLOUT_PLIB_XFILE_XSYS_FIND_H_ #define FALLOUT_PLIB_XFILE_XSYS_FIND_H_ #include #if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #else #include #endif // NOTE: This structure is significantly different from what was in the // original code. Watcom provides opendir/readdir/closedir implementations, // that use Win32 FindFirstFile/FindNextFile under the hood, which in turn // is designed to deal with patterns. // // The first attempt was to use `dirent` implementation by Toni Ronkko // (https://github.com/tronkko/dirent), however it appears to be incompatible // with what is provided by Watcom. Toni's implementation adds `*` wildcard // unconditionally implying `opendir` accepts directory name only, which I // guess is fine when your goal is compliance with POSIX implementation. // However in Watcom `opendir` can handle file patterns gracefully. The problem // can be seen during game startup when cleaning MAPS directory using // MAPS\*.SAV pattern. Toni's implementation tries to convert that to pattern // for Win32 API, thus making it MAPS\*.SAV\*, which is obviously incorrect // path/pattern for any implementation. // // Eventually I've decided to go with compiler-specific implementation, keeping // original implementation for Watcom (not tested). I'm not sure it will work // in other compilers, so for now just stick with the error. typedef struct DirectoryFileFindData { #if defined(_WIN32) HANDLE hFind; WIN32_FIND_DATAA ffd; #else DIR* dir; struct dirent* entry; #endif } DirectoryFileFindData; bool xsys_findfirst(const char* path, DirectoryFileFindData* findData); bool xsys_findnext(DirectoryFileFindData* findData); bool xsys_findclose(DirectoryFileFindData* findData); static inline bool fileFindIsDirectory(DirectoryFileFindData* findData) { #if defined(_WIN32) return (findData->ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; #elif defined(__WATCOMC__) return (findData->entry->d_attr & _A_SUBDIR) != 0; #else #error Not implemented #endif } static inline char* fileFindGetName(DirectoryFileFindData* findData) { #if defined(_WIN32) return findData->ffd.cFileName; #else #error Not implemented #endif } #endif /* FALLOUT_PLIB_XFILE_XSYS_FIND_H_ */ ================================================ FILE: src/sound_decoder.c ================================================ // NOTE: Functions in these module are somewhat different from what can be seen // in IDA because of two new helper functions that deal with incoming bits. I // bet something like these were implemented via function-like macro in the // same manner zlib deals with bits. The pattern is so common in this module so // I made an exception and extracted it into separate functions to increase // readability. #include "sound_decoder.h" #include #include #include static inline void soundDecoderRequireBits(SoundDecoder* soundDecoder, int bits); static inline void soundDecoderDropBits(SoundDecoder* soundDecoder, int bits); // 0x51E328 int gSoundDecodersCount = 0; // 0x51E32C bool _inited_ = false; // 0x51E330 DECODINGPROC _ReadBand_tbl[32] = { _ReadBand_Fmt0_, _ReadBand_Fail_, _ReadBand_Fail_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt3_16_, _ReadBand_Fmt17_, _ReadBand_Fmt18_, _ReadBand_Fmt19_, _ReadBand_Fmt20_, _ReadBand_Fmt21_, _ReadBand_Fmt22_, _ReadBand_Fmt23_, _ReadBand_Fmt24_, _ReadBand_Fail_, _ReadBand_Fmt26_, _ReadBand_Fmt27_, _ReadBand_Fail_, _ReadBand_Fmt29_, _ReadBand_Fail_, _ReadBand_Fail_, }; // 0x6AD960 unsigned char _pack11_2[128]; // 0x6AD9E0 unsigned char _pack3_3[32]; // 0x6ADA00 unsigned short word_6ADA00[128]; // 0x6ADB00 unsigned char* _AudioDecoder_scale0; // 0x6ADB04 unsigned char* _AudioDecoder_scale_tbl; // 0x4D3BB0 bool soundDecoderPrepare(SoundDecoder* soundDecoder, SoundDecoderReadProc* readProc, int fileHandle) { soundDecoder->readProc = readProc; soundDecoder->fd = fileHandle; soundDecoder->bufferIn = (unsigned char*)malloc(SOUND_DECODER_IN_BUFFER_SIZE); if (soundDecoder->bufferIn == NULL) { return false; } soundDecoder->bufferInSize = SOUND_DECODER_IN_BUFFER_SIZE; soundDecoder->remainingInSize = 0; return true; } // 0x4D3BE0 unsigned char soundDecoderReadNextChunk(SoundDecoder* soundDecoder) { soundDecoder->remainingInSize = soundDecoder->readProc(soundDecoder->fd, soundDecoder->bufferIn, soundDecoder->bufferInSize); if (soundDecoder->remainingInSize == 0) { memset(soundDecoder->bufferIn, 0, soundDecoder->bufferInSize); soundDecoder->remainingInSize = soundDecoder->bufferInSize; } soundDecoder->nextIn = soundDecoder->bufferIn; soundDecoder->remainingInSize -= 1; return *soundDecoder->nextIn++; } // 0x4D3C78 void _init_pack_tables() { int i; int j; int m; if (_inited_) { return; } for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { for (m = 0; m < 3; m++) { _pack3_3[i + j * 3 + m * 9] = i + j * 4 + m * 16; } } } for (i = 0; i < 5; i++) { for (j = 0; j < 5; j++) { for (m = 0; m < 5; m++) { word_6ADA00[i + j * 5 + m * 25] = i + j * 8 + m * 64; } } } for (i = 0; i < 11; i++) { for (j = 0; j < 11; j++) { _pack11_2[i + j * 11] = i + j * 16; } } _inited_ = true; } // 0x4D3D9C int _ReadBand_Fail_(SoundDecoder* soundDecoder, int offset, int bits) { return 0; } // 0x4D3DA0 int _ReadBand_Fmt0_(SoundDecoder* soundDecoder, int offset, int bits) { int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { *p = 0; p += soundDecoder->field_24; i--; } return 1; } // 0x4D3DC8 int _ReadBand_Fmt3_16_(SoundDecoder* soundDecoder, int offset, int bits) { int value; int v14; short* base = (short*)_AudioDecoder_scale0; base += UINT_MAX << (bits - 1); int* p = (int*)soundDecoder->field_34; p += offset; v14 = (1 << bits) - 1; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, bits); value = soundDecoder->hold; soundDecoderDropBits(soundDecoder, bits); *p = base[v14 & value]; p += soundDecoder->field_24; i--; } return 1; } // 0x4D3E90 int _ReadBand_Fmt17_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 3); int value = soundDecoder->hold & 0xFF; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else if (!(value & 0x02)) { soundDecoderDropBits(soundDecoder, 2); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 3); if (value & 0x04) { *p = base[1]; } else { *p = base[-1]; } p += soundDecoder->field_24; i--; } } return 1; } // 0x4D3F98 int _ReadBand_Fmt18_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 2); int value = soundDecoder->hold; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { return 1; } } else { soundDecoderDropBits(soundDecoder, 2); if (value & 0x02) { *p = base[1]; } else { *p = base[-1]; } p += soundDecoder->field_24; i--; } } return 1; } // 0x4D4068 int _ReadBand_Fmt19_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; base -= 1; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 5); int value = soundDecoder->hold & 0x1F; soundDecoderDropBits(soundDecoder, 5); value = _pack3_3[value]; *p = base[value & 0x03]; p += soundDecoder->field_24; if (--i == 0) { break; } *p = base[(value >> 2) & 0x03]; p += soundDecoder->field_24; if (--i == 0) { break; } *p = base[value >> 4]; p += soundDecoder->field_24; i--; } return 1; } // 0x4D4158 int _ReadBand_Fmt20_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 4); int value = soundDecoder->hold & 0xFF; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else if (!(value & 0x02)) { soundDecoderDropBits(soundDecoder, 2); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 4); if (value & 0x08) { if (value & 0x04) { *p = base[2]; } else { *p = base[1]; } } else { if (value & 0x04) { *p = base[-1]; } else { *p = base[-2]; } } p += soundDecoder->field_24; i--; } } return 1; } // 0x4D4254 int _ReadBand_Fmt21_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 3); int value = soundDecoder->hold & 0xFF; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 3); if (value & 0x04) { if (value & 0x02) { *p = base[2]; } else { *p = base[1]; } } else { if (value & 0x02) { *p = base[-1]; } else { *p = base[-2]; } } p += soundDecoder->field_24; i--; } } return 1; } // 0x4D4338 int _ReadBand_Fmt22_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; base -= 2; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 7); int value = soundDecoder->hold & 0x7F; soundDecoderDropBits(soundDecoder, 7); value = word_6ADA00[value]; *p = base[value & 7]; p += soundDecoder->field_24; if (--i == 0) { break; } *p = base[((value >> 3) & 7)]; p += soundDecoder->field_24; if (--i == 0) { break; } *p = base[value >> 6]; p += soundDecoder->field_24; if (--i == 0) { break; } } return 1; } // 0x4D4434 int _ReadBand_Fmt23_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 5); int value = soundDecoder->hold; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else if (!(value & 0x02)) { soundDecoderDropBits(soundDecoder, 2); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else if (!(value & 0x04)) { soundDecoderDropBits(soundDecoder, 4); if (value & 0x08) { *p = base[1]; } else { *p = base[-1]; } p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 5); value >>= 3; value &= 0x03; if (value >= 2) { value += 3; } *p = base[value - 3]; p += soundDecoder->field_24; i--; } } return 1; } // 0x4D4584 int _ReadBand_Fmt24_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 4); int value = soundDecoder->hold & 0xFF; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else if (!(value & 0x02)) { soundDecoderDropBits(soundDecoder, 3); if (value & 0x04) { *p = base[1]; } else { *p = base[-1]; } p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 4); value >>= 2; value &= 0x03; if (value >= 2) { value += 3; } *p = base[value - 3]; p += soundDecoder->field_24; i--; } } return 1; } // 0x4D4698 int _ReadBand_Fmt26_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 5); int value = soundDecoder->hold; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else if (!(value & 0x02)) { soundDecoderDropBits(soundDecoder, 2); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 5); value >>= 2; value &= 0x07; if (value >= 4) { value += 1; } *p = base[value - 4]; p += soundDecoder->field_24; i--; } } return 1; } // 0x4D47A4 int _ReadBand_Fmt27_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 4); int value = soundDecoder->hold; if (!(value & 0x01)) { soundDecoderDropBits(soundDecoder, 1); *p = 0; p += soundDecoder->field_24; if (--i == 0) { break; } } else { soundDecoderDropBits(soundDecoder, 4); value >>= 1; value &= 0x07; if (value >= 4) { value += 1; } *p = base[value - 4]; p += soundDecoder->field_24; i--; } } return 1; } // 0x4D4870 int _ReadBand_Fmt29_(SoundDecoder* soundDecoder, int offset, int bits) { short* base = (short*)_AudioDecoder_scale0; int* p = (int*)soundDecoder->field_34; p += offset; int i = soundDecoder->field_28; while (i != 0) { soundDecoderRequireBits(soundDecoder, 7); int value = soundDecoder->hold & 0x7F; soundDecoderDropBits(soundDecoder, 7); value = _pack11_2[value]; *p = base[(value & 0x0F) - 5]; p += soundDecoder->field_24; if (--i == 0) { break; } *p = base[(value >> 4) - 5]; p += soundDecoder->field_24; if (--i == 0) { break; } } return 1; } // 0x4D493C int _ReadBands_(SoundDecoder* soundDecoder) { int v9; int v15; int v17; int v19; unsigned short* v18; int v21; DECODINGPROC fn; soundDecoderRequireBits(soundDecoder, 4); v9 = soundDecoder->hold & 0xF; soundDecoderDropBits(soundDecoder, 4); soundDecoderRequireBits(soundDecoder, 16); v15 = soundDecoder->hold & 0xFFFF; soundDecoderDropBits(soundDecoder, 16); v17 = 1 << v9; v18 = (unsigned short*)_AudioDecoder_scale0; v19 = v17; v21 = 0; while (v19--) { *v18++ = v21; v21 += v15; } v18 = (unsigned short*)_AudioDecoder_scale0; v19 = v17; v21 = -v15; while (v19--) { v18--; *v18 = v21; v21 -= v15; } _init_pack_tables(); for (int index = 0; index < soundDecoder->field_24; index++) { soundDecoderRequireBits(soundDecoder, 5); int bits = soundDecoder->hold & 0x1F; soundDecoderDropBits(soundDecoder, 5); fn = _ReadBand_tbl[bits]; if (!fn(soundDecoder, index, bits)) { return 0; } } return 1; } // 0x4D4ADC void _untransform_subband0(unsigned char* a1, unsigned char* a2, int a3, int a4) { short* p; p = (short*)a2; p += a3; if (a4 == 2) { int i = a3; while (i != 0) { i--; } } else if (a4 == 4) { int v31 = a3; int* v9 = (int*)a2; v9 += a3; int* v10 = (int*)a2; v10 += a3 * 3; int* v11 = (int*)a2; v11 += a3 * 2; while (v31 != 0) { int* v33 = (int*)a2; int* v34 = (int*)a1; int v12 = *v34 >> 16; int v13 = *v33; *v33 = (int)(*(short*)v34) + 2 * v12 + v13; int v14 = *v9; *v9 = 2 * v13 - v12 - v14; int v15 = *v11; *v11 = 2 * v14 + v15 + v13; int v16 = *v10; *v10 = 2 * v15 - v14 - v16; v10++; v11++; v9++; *(short*)a1 = v15 & 0xFFFF; *(short*)(a1 + 2) = v16 & 0xFFFF; a1 += 4; a2 += 4; v31--; } } else { int v30 = a4 >> 1; int v32 = a3; while (v32 != 0) { int* v19 = (int*)a2; int v20; int v22; if (v30 & 0x01) { } else { v20 = (int)*(short*)a1; v22 = *(int*)a1 >> 16; } int v23 = v30 >> 1; while (--v23 != -1) { int v24 = *v19; *v19 += 2 * v22 + v20; v19 += a3; int v26 = *v19; *v19 = 2 * v24 - v22 - v26; v19 += a3; v20 = *v19; *v19 += 2 * v26 + v24; v19 += a3; v22 = *v19; *v19 = 2 * v20 - v26 - v22; v19 += a3; } *(short*)a1 = v20 & 0xFFFF; *(short*)(a1 + 2) = v22 & 0xFFFF; a1 += 4; a2 += 4; v32--; } } } // 0x4D4D1C void _untransform_subband(unsigned char* a1, unsigned char* a2, int a3, int a4) { int v13; int* v14; int* v25; int* v26; int v15; int v16; int v17; int* v18; int v19; int* v20; int* v21; v26 = (int*)a1; v25 = (int*)a2; if (a4 == 4) { unsigned char* v4 = a2 + 4 * a3; unsigned char* v5 = a2 + 3 * a3; unsigned char* v6 = a2 + 2 * a3; int v7; int v8; int v9; int v10; int v11; while (a3--) { v7 = *(unsigned int*)(v26 + 4); v8 = *(unsigned int*)v25; *(unsigned int*)v25 = *(unsigned int*)v26 + 2 * v7; v9 = *(unsigned int*)v4; *(unsigned int*)v4 = 2 * v8 - v7 - v9; v10 = *(unsigned int*)v6; v5 += 4; *v6 += 2 * v9 + v8; v11 = *(unsigned int*)(v5 - 4); v6 += 4; *(unsigned int*)(v5 - 4) = 2 * v10 - v9 - v11; v4 += 4; *(unsigned int*)v26 = v10; *(unsigned int*)(v26 + 4) = v11; v26 += 2; v25 += 1; } } else { int v24 = a3; while (v24 != 0) { v13 = a4 >> 2; v14 = v25; v15 = v26[0]; v16 = v26[1]; while (--v13 != -1) { v17 = *v14; *v14 += 2 * v16 + v15; v18 = v14 + a3; v19 = *v18; *v18 = 2 * v17 - v16 - v19; v20 = v18 + a3; v15 = *v20; *v20 += 2 * v19 + v17; v21 = v20 + a3; v16 = *v21; *v21 = 2 * v15 - v19 - v16; v14 = v21 + a3; } v26[0] = v15; v26[1] = v16; v26 += 2; v25 += 1; v24--; } } } // 0x4D4E80 void _untransform_all(SoundDecoder* soundDecoder) { int v8; unsigned char* ptr; int v3; int v4; unsigned char* j; int v6; int* v5; if (!soundDecoder->field_20) { return; } ptr = soundDecoder->field_34; v8 = soundDecoder->field_28; while (v8 > 0) { v3 = soundDecoder->field_24 >> 1; v4 = soundDecoder->field_38; if (v4 > v8) { v4 = v8; } v4 *= 2; _untransform_subband0(soundDecoder->field_30, ptr, v3, v4); v5 = (int*)ptr; for (v6 = 0; v6 < v4; v6++) { *v5 += 1; v5 += v3; } j = 4 * v3 + soundDecoder->field_30; while (1) { v3 >>= 1; v4 *= 2; if (v3 == 0) { break; } _untransform_subband(j, ptr, v3, v4); j += 8 * v3; } ptr += soundDecoder->field_3C * 4; v8 -= soundDecoder->field_38; } } // 0x4D4FA0 size_t soundDecoderDecode(SoundDecoder* soundDecoder, void* buffer, size_t size) { unsigned char* dest; unsigned char* v5; int v6; int v4; dest = (unsigned char*)buffer; v4 = 0; v5 = soundDecoder->field_4C; v6 = soundDecoder->field_50; size_t bytesRead; for (bytesRead = 0; bytesRead < size; bytesRead += 2) { if (!v6) { if (!soundDecoder->field_48) { break; } if (!_ReadBands_(soundDecoder)) { break; } _untransform_all(soundDecoder); soundDecoder->field_48 -= soundDecoder->field_2C; soundDecoder->field_4C = soundDecoder->field_34; soundDecoder->field_50 = soundDecoder->field_2C; if (soundDecoder->field_48 < 0) { soundDecoder->field_50 += soundDecoder->field_48; soundDecoder->field_48 = 0; } v5 = soundDecoder->field_4C; v6 = soundDecoder->field_50; } int v13 = *(int*)v5; v5 += 4; *(unsigned short*)(dest + bytesRead) = (v13 >> soundDecoder->field_20) & 0xFFFF; v6--; } soundDecoder->field_4C = v5; soundDecoder->field_50 = v6; return bytesRead; } // 0x4D5048 void soundDecoderFree(SoundDecoder* soundDecoder) { if (soundDecoder->bufferIn != NULL) { free(soundDecoder->bufferIn); } if (soundDecoder->field_30 != NULL) { free(soundDecoder->field_30); } if (soundDecoder->field_34 != NULL) { free(soundDecoder->field_34); } free(soundDecoder); gSoundDecodersCount--; if (gSoundDecodersCount == 0) { if (_AudioDecoder_scale_tbl != NULL) { free(_AudioDecoder_scale_tbl); _AudioDecoder_scale_tbl = NULL; } } } // 0x4D50A8 SoundDecoder* soundDecoderInit(SoundDecoderReadProc* readProc, int fileHandle, int* out_a3, int* out_a4, int* out_a5) { int v14; int v20; int v73; SoundDecoder* soundDecoder = (SoundDecoder*)malloc(sizeof(*soundDecoder)); if (soundDecoder == NULL) { return NULL; } memset(soundDecoder, 0, sizeof(*soundDecoder)); gSoundDecodersCount++; if (!soundDecoderPrepare(soundDecoder, readProc, fileHandle)) { goto L66; } soundDecoder->hold = 0; soundDecoder->bits = 0; soundDecoderRequireBits(soundDecoder, 24); v14 = soundDecoder->hold; soundDecoderDropBits(soundDecoder, 24); if ((v14 & 0xFFFFFF) != 0x32897) { goto L66; } soundDecoderRequireBits(soundDecoder, 8); v20 = soundDecoder->hold; soundDecoderDropBits(soundDecoder, 8); if (v20 != 1) { goto L66; } soundDecoderRequireBits(soundDecoder, 16); soundDecoder->field_48 = soundDecoder->hold & 0xFFFF; soundDecoderDropBits(soundDecoder, 16); soundDecoderRequireBits(soundDecoder, 16); soundDecoder->field_48 |= (soundDecoder->hold & 0xFFFF) << 16; soundDecoderDropBits(soundDecoder, 16); soundDecoderRequireBits(soundDecoder, 16); soundDecoder->field_40 = soundDecoder->hold & 0xFFFF; soundDecoderDropBits(soundDecoder, 16); soundDecoderRequireBits(soundDecoder, 16); soundDecoder->field_44 = soundDecoder->hold & 0xFFFF; soundDecoderDropBits(soundDecoder, 16); soundDecoderRequireBits(soundDecoder, 4); soundDecoder->field_20 = soundDecoder->hold & 0x0F; soundDecoderDropBits(soundDecoder, 4); soundDecoderRequireBits(soundDecoder, 12); soundDecoder->field_24 = 1 << soundDecoder->field_20; soundDecoder->field_28 = soundDecoder->hold & 0x0FFF; soundDecoder->field_2C = soundDecoder->field_28 * soundDecoder->field_24; soundDecoderDropBits(soundDecoder, 12); if (soundDecoder->field_20 != 0) { v73 = 3 * soundDecoder->field_24 / 2 - 2; } else { v73 = 0; } soundDecoder->field_38 = 2048 / soundDecoder->field_24 - 2; if (soundDecoder->field_38 < 1) { soundDecoder->field_38 = 1; } soundDecoder->field_3C = soundDecoder->field_38 * soundDecoder->field_24; if (v73 != 0) { soundDecoder->field_30 = (unsigned char*)malloc(sizeof(unsigned char*) * v73); if (soundDecoder->field_30 == NULL) { goto L66; } memset(soundDecoder->field_30, 0, sizeof(unsigned char*) * v73); } soundDecoder->field_34 = (unsigned char*)malloc(sizeof(unsigned char*) * soundDecoder->field_2C); if (soundDecoder->field_34 == NULL) { goto L66; } soundDecoder->field_50 = 0; if (gSoundDecodersCount == 1) { _AudioDecoder_scale_tbl = (unsigned char*)malloc(0x20000); _AudioDecoder_scale0 = _AudioDecoder_scale_tbl + 0x10000; } *out_a3 = soundDecoder->field_40; *out_a4 = soundDecoder->field_44; *out_a5 = soundDecoder->field_48; return soundDecoder; L66: soundDecoderFree(soundDecoder); *out_a3 = 0; *out_a4 = 0; *out_a5 = 0; return 0; } static inline void soundDecoderRequireBits(SoundDecoder* soundDecoder, int bits) { while (soundDecoder->bits < bits) { soundDecoder->remainingInSize--; unsigned char ch; if (soundDecoder->remainingInSize < 0) { ch = soundDecoderReadNextChunk(soundDecoder); } else { ch = *soundDecoder->nextIn++; } soundDecoder->hold |= ch << soundDecoder->bits; soundDecoder->bits += 8; } } static inline void soundDecoderDropBits(SoundDecoder* soundDecoder, int bits) { soundDecoder->hold >>= bits; soundDecoder->bits -= bits; } ================================================ FILE: src/sound_decoder.h ================================================ #ifndef SOUND_DECODER_H #define SOUND_DECODER_H #include #include #define SOUND_DECODER_IN_BUFFER_SIZE (512) typedef int(SoundDecoderReadProc)(int fileHandle, void* buffer, unsigned int size); typedef struct SoundDecoder { SoundDecoderReadProc* readProc; int fd; unsigned char* bufferIn; size_t bufferInSize; // Next input byte. unsigned char* nextIn; // Number of bytes remaining in the input buffer. int remainingInSize; // Bit accumulator. int hold; // Number of bits in bit accumulator. int bits; int field_20; int field_24; int field_28; int field_2C; unsigned char* field_30; unsigned char* field_34; int field_38; int field_3C; int field_40; int field_44; int field_48; unsigned char* field_4C; int field_50; } SoundDecoder; #if _WIN32 static_assert(sizeof(SoundDecoder) == 84, "wrong size"); #endif typedef int (*DECODINGPROC)(SoundDecoder* soundDecoder, int offset, int bits); extern int gSoundDecodersCount; extern bool _inited_; extern DECODINGPROC _ReadBand_tbl[32]; extern unsigned char _pack11_2[128]; extern unsigned char _pack3_3[32]; extern unsigned short word_6ADA00[128]; extern unsigned char* _AudioDecoder_scale0; extern unsigned char* _AudioDecoder_scale_tbl; bool soundDecoderPrepare(SoundDecoder* a1, SoundDecoderReadProc* readProc, int fileHandle); unsigned char soundDecoderReadNextChunk(SoundDecoder* a1); void _init_pack_tables(); int _ReadBand_Fail_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt0_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt3_16_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt17_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt18_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt19_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt20_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt21_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt22_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt23_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt24_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt26_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt27_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBand_Fmt29_(SoundDecoder* soundDecoder, int offset, int bits); int _ReadBands_(SoundDecoder* ptr); void _untransform_subband0(unsigned char* a1, unsigned char* a2, int a3, int a4); void _untransform_subband(unsigned char* a1, unsigned char* a2, int a3, int a4); void _untransform_all(SoundDecoder* a1); size_t soundDecoderDecode(SoundDecoder* soundDecoder, void* buffer, size_t size); void soundDecoderFree(SoundDecoder* soundDecoder); SoundDecoder* soundDecoderInit(SoundDecoderReadProc* readProc, int fileHandle, int* out_a3, int* out_a4, int* out_a5); #endif /* SOUND_DECODER_H */ ================================================ FILE: third_party/fpattern/CMakeLists.txt ================================================ include(FetchContent) FetchContent_Declare(fpattern GIT_REPOSITORY "https://github.com/Loadmaster/fpattern" GIT_TAG "v1.9" ) FetchContent_GetProperties(fpattern) if (NOT fpattern_POPULATED) FetchContent_Populate(fpattern) endif() add_library(fpattern STATIC "${fpattern_SOURCE_DIR}/debug.h" "${fpattern_SOURCE_DIR}/fpattern.c" "${fpattern_SOURCE_DIR}/fpattern.h" ) set(FPATTERN_LIBRARY "fpattern" PARENT_SCOPE) set(FPATTERN_INCLUDE_DIR "${fpattern_SOURCE_DIR}" PARENT_SCOPE) ================================================ FILE: third_party/fpattern/LICENSE ================================================ The MIT License (MIT) Copyright ©1997-2001 by David R Tribble. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: third_party/fpattern/README.md ================================================ # fpattern Fallout 2 uses `fpattern` v1.07 (see `0x4EB990`) to find entries in .DAT files. The closest version I was able to find is v1.08 on the author's website ([fpattern.c](http://david.tribble.com/src/fpattern.c), [fpattern.h](http://david.tribble.com/src/fpattern.h)). It's not very safe to use such direct links, and I'm not sure if it's possible to download these files via `FetchContent`. Luckily there is a newer v1.9 on [GitHub](https://github.com/Loadmaster/fpattern). ================================================ FILE: third_party/zlib/CMakeLists.txt ================================================ include(FetchContent) FetchContent_Declare(zlib GIT_REPOSITORY "https://github.com/madler/zlib" GIT_TAG "v1.2.11" ) FetchContent_GetProperties(zlib) if (NOT zlib_POPULATED) FetchContent_Populate(zlib) endif() add_subdirectory(${zlib_SOURCE_DIR} ${zlib_BINARY_DIR} EXCLUDE_FROM_ALL) set(ZLIB_LIBRARIES zlibstatic PARENT_SCOPE) set(ZLIB_INCLUDE_DIRS ${zlib_SOURCE_DIR} ${zlib_BINARY_DIR} PARENT_SCOPE) ================================================ FILE: third_party/zlib/LICENSE ================================================ Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. Jean-loup Gailly Mark Adler jloup@gzip.org madler@alumni.caltech.edu ================================================ FILE: third_party/zlib/README.md ================================================ # zlib Fallout 2 uses `zlib` v1.1.1 (as seen at `0x4E18A0`, `0x4ED2F0`, and other various places) to decompress data embedded in .DAT files. There is no point in using such old version, because zlib is highlighly optimized, portable, and provides backward compatibility.